我打算将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
)元组的元组。所以在这方面它将是没有
差。
非常欢迎适当的解决方案。
我对我的第一个解决方案有几点评论。
我原以为是构造函数
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]]")
的调用只是
((),())
不确定
是什么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'>
答案 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()
的完整性,只实施了tuple
与list
相比较的限制,这些限制立即浮现在脑海中。
我用我的增强版PyYAML测试了这个:ruamel.yaml,但这在PyYAML本身也应该有效。