PyYAML元组元组的自定义标记

时间:2013-01-24 19:16:17

标签: python yaml pyyaml

我打算将PyYAML用于配置文件。一些项目 在该配置文件中是元组的Python元组。所以,我需要一个 方便的方式来代表他们。一个人可以代表Python的元组 使用PyYAML

的元组如下
print yaml.load("!!python/tuple [ !!python/tuple [1, 2], !!python/tuple [3, 4]]")

然而,对于长序列来说,这不是方便的表示法 项目。我认为应该可以定义一个自定义标签,比如 蟒蛇/ 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年?

  3. 首次尝试

    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'>
    

1 个答案:

答案 0 :(得分:2)

首先从问题2开始:2002年是2002年9月1日版YAML 1.0 draft

中引入的这种标签的一年

问题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全部加载到原始列表列表,因为seq(uence)是内置类型。

我不认为扩展yaml的语法(例如()表示元组是个好主意。要最小化标记,请将示例缩减为:

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

并添加一个构造函数:

yaml.add_constructor("!tuple", construct_tuple)

但是这会将问题推到创建construct_tuple函数。序列的一个(在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以及非嵌套使用,并且在使用!!seq作为标记时,有一些显式重写以获取元组内的列表。


我可能没有检查Tuple()的完整性,只实施了tuplelist相比较的限制,这些限制立即浮现在脑海中。 我用我的增强版PyYAML测试了这个:ruamel.yaml,但这在PyYAML本身也应该有效。