1

我打算将 PyYAML 用于配置文件。该配置文件中的一些项目是元组的 Python 元组。所以,我需要一种方便的方式来表示它们。使用 PyYAML 可以如下表示元组的 Python 元组

print yaml.load("!!python/tuple [ !!python/tuple [1, 2], !!python/tuple [3, 4]]")

但是,对于长序列的项目,这不是方便的表示法。我认为应该可以定义一个自定义标签,比如 python/tuple_of_tuples。即类似的东西

yaml.load("!!python/tuple_of_tuples [[1,2], [3,4]]")

请参阅下面我第一次尝试定义它,通过模仿 python/tuple 的定义方式,并尝试进行类似的子类化。它失败了,但我想知道我在追求什么。我有第二次尝试,但它是作弊,因为它只是调用eval.

如果我找不到更好的东西,我会使用它。但是,YAML 旨在替代使用 INI 文件的 ConfigObj,它的功能远不如 YAML,我eval对元组的元组使用了相同的方法(即 )。所以在这方面它不会更糟。

一个适当的解决方案将是最受欢迎的。

我对我的第一个解决方案有几点意见。

  1. 我原以为构造函数 construct_python_tuple_of_tuples会返回完整的结构,但实际上它似乎返回一个空结构,如下所示

    ([], [])
    

    我跟踪了这​​些调用,调用之后似乎发生了很多复杂的事情construct_python_tuple_of_tuples

    返回的值是整数列表的元组,因此非常接近所需的结果。因此,结构必须稍后完成。

    该行与

    tuple([tuple(t) for t in x])
    

    是我试图将元组列表强制为元组的元组,但如果我从 中返回它construct_python_tuple_of_tuples,那么结果调用yaml.load("!!python/tuple_of_tuples [[1,2], [3,4]]")就是

    ((),())
    
  2. 不知道是什么

    yaml.org,2002
    

    为什么是 2002 年?

第一次尝试

import yaml
from yaml.constructor import Constructor

def construct_python_tuple_of_tuples(self, node):
     # Complete content of construct_python_tuple
     # is
     # return tuple(self.construct_sequence(node))

     print "node", node
     x = tuple(self.construct_sequence(node))
     print "x", x
     foo = tuple([tuple(t) for t in x])
     print "foo", foo
     return x

Constructor.construct_python_tuple_of_tuples =
construct_python_tuple_of_tuples

Constructor.add_constructor(
         u'tag:yaml.org,2002:python/tuple_of_tuples',
         Constructor.construct_python_tuple_of_tuples)

y = yaml.load("!!python/tuple_of_tuples [[1,2], [3,4]]")
print "y", y, type(y)
print y[0], type(y[0])
print y[0][0], type(y[0][0])

结果是

node SequenceNode(tag=u'tag:yaml.org,2002:python/tuple_of_tuples',
value=[SequenceNode(tag=u'tag:yaml.org,2002:seq',
value=[ScalarNode(tag=u'tag:yaml.org,2002:int', value=u'1'),
ScalarNode(tag=u'tag:yaml.org,2002:int', value=u'2')]),
SequenceNode(tag=u'tag:yaml.org,2002:seq',
value=[ScalarNode(tag=u'tag:yaml.org,2002:int', value=u'3'),
ScalarNode(tag=u'tag:yaml.org,2002:int', value=u'4')])])

x ([], [])

foo ((), ())

y ([1, 2], [3, 4]) <type 'tuple'>

y[0] [1, 2] <type 'list'>

y[0][0] 1 <type 'int'>

第二次尝试

import yaml
from yaml import YAMLObject, Loader, Dumper

class TupleOfTuples(YAMLObject):
    yaml_loader = Loader
    yaml_dumper = Dumper

    yaml_tag = u'!TupleOfTuples'
    #yaml_flow_style = ...

    @classmethod
    def from_yaml(cls, loader, node):
        import ast
        print "node", node
    print "node.value", node.value, type(node.value)
        return ast.literal_eval(node.value)

    @classmethod
    def to_yaml(cls, dumper, data):
        return node

t = yaml.load("!TupleOfTuples ((1, 2), (3, 4))")
print "t", t, type(t)

结果是:

node ScalarNode(tag=u'!TupleOfTuples', value=u'((1, 2), (3, 4))')
node.value ((1, 2), (3, 4)) <type 'unicode'>
t ((1, 2), (3, 4)) <type 'tuple'>
4

1 回答 1

2

首先从问题 2 开始:2002 年是这种标签在 2002 年 9 月 1 日版本的YAML 1.0 草案中引入的年份

问题1更复杂。如果你这样做:

from __future__ import print_function

import yaml

lol = [[1,2], [3,4]]  # list of lists
print(yaml.dump(lol))

你得到(A):

[[1, 2], [3, 4]]

但实际上这是 (B) 的缩写:

!!seq [
  !!seq [
    !!int "1",
    !!int "2",
  ],
  !!seq [
    !!int "3",
    !!int "4",
  ],
]

(C) 的缩写:

!<tag:yaml.org,2002:seq> [
  !<tag:yaml.org,2002:seq> [
    !<tag:yaml.org,2002:int> "1",
    !<tag:yaml.org,2002:int> "2",
  ],
  !<tag:yaml.org,2002:seq> [
    !<tag:yaml.org,2002:int> "3",
    !<tag:yaml.org,2002:int> "4",
  ],
]

A、B 和 C 都加载到 list 的原始列表,因为 seq(uence) 是内置类型。

我不认为扩展 yaml 的语法(例如()指示元组将是一个好主意。要最小化标签,您可以将示例简化为:

yaml_in = "!tuple [ !tuple [1, 2], !tuple [3, 4]]"

并添加一个构造函数:

yaml.add_constructor("!tuple", construct_tuple)

但这将问题推向了创建construct_tuple函数。一个序列(in constructor.py)是:

def construct_yaml_seq(self, node):
    data = []
    yield data
    data.extend(self.construct_sequence(node))

但是你不能仅仅替换其中的[]()因为通过扩展它来改变元组是行不通的(这两个步骤创建的原因是yield,例如,允许在复杂类型中循环引用,如序列和映射)。

您应该定义一个Tuple()在“锁定”之前表现得像列表的类(您将在构造结束时执行此操作),从那时起它应该表现得像一个元组(即不再进行修改)。以下是在没有子类化的情况下这样做的yaml.YAMLObject,因此您必须显式地提供和注册该类的构造函数和表示器。

class Tuple(list):

    def _lock(self):
        if hasattr(self, '_is_locked'):
            return
        self._is_locked = True
        self.append = self._append
        self.extend = self._extend

    def _append(self, item):
        raise AttributeError("'Tuple' object has no attribute 'append'")

    def _extend(self, items):
        raise AttributeError("'Tuple' object has no attribute 'extend'")

    def __str__(self):
        return '(' + ', '.join((str(e) for e in self)) + ')'

    # new style class cannot assign something to special method
    def __setitem__(self, key, value):
        if getattr(self, '_is_locked', False):
            raise TypeError("'Tuple' object does not support item assignment")
        list.__setitem__(self, key, value)

    def __delitem__(self, key, value):
        if getattr(self, '_is_locked', False):
            raise TypeError("'Tuple' object does not support item deletion")
        list.__delitem__(self, key, value)

    @staticmethod
    def _construct_tuple(loader, data):
        result = Tuple()
        yield result
        result.extend(loader.construct_sequence(data))
        result._lock()

    @staticmethod
    def _represent_tuple(dumper, node):
        return dumper.represent_sequence("!tuple", node)

# let yaml know how to handle this
yaml.add_constructor("!tuple", Tuple._construct_tuple)
yaml.add_representer(Tuple, Tuple._represent_tuple)

有了它,您可以执行以下操作:

yaml_in = "!tuple [ !tuple [1, 2], !tuple [3, 4]]"
#yaml_in = "!tuple [1, 2]"

data = yaml.load(yaml_in)
print(data)
print(data[1][0])
print(type(data))

要得到:

((1, 2), (3, 4))
3
<class '__main__.Tuple'>

这不是一个真实的tuple,但它不允许类似list的动作。以下活动都会引发相应的错误:

# test appending to the tuple,
try:
    data.append(Tuple([5, 6]))
except AttributeError:
    pass
else:
    raise NotImplementedError
# test extending the tuple,
try:
    data.extend([5, 6])
except AttributeError:
    pass
else:
    raise NotImplementedError
# test replacement of an item
try:
    data[0] = Tuple([5, 6])
except TypeError:
    pass
else:
    raise NotImplementedError
# test deletion of an item
try:
    del data[0]
except TypeError:
    pass
else:
    raise NotImplementedError

最后你可以这样做:

print(yaml.dump(data, default_flow_style=True))

对于以下输出:

!tuple [!tuple [1, 2], !tuple [3, 4]]

如果你真的想!tuple [[1, 2], [3, 4]]创建一个元组的元组,你可以通过在Baseloader类中保持上下文状态并根据上下文yaml覆盖从序列到元组或列表构造python对象的方法来实现。这可能必须是一堆上下文状态,以允许嵌套使用 !tuple 以及非嵌套使用,以及在使用!!seqas 标记时进行一些显式覆盖以获取元组中的列表。


我可能没有检查Tuple()完整性,只是实施了与立即想到的tuple相比的限制。 我用我的增强版 PyYAML 进行了测试:ruamel.yaml,但这在 PyYAML 本身中应该是一样的。list

于 2015-04-14T06:07:12.550 回答