通过PyYAML序列化命名元组
我在寻找一种合理的方法,用PyYAML将命名元组序列化为YAML格式。
有几点我不想做:
不想在创建命名元组时动态调用构造函数、表示器或解析器。这些YAML文件可能会被存储并在以后重新加载,所以我不能依赖于在恢复时存在相同的运行环境。
不想在全局范围内注册命名元组。
不想依赖命名元组有唯一的名称。
我在想类似这样的方案:
class namedtuple(object):
def __new__(cls, *args, **kwargs):
x = collections.namedtuple(*args, **kwargs)
class New(x):
def __getstate__(self):
return {
"name": self.__class__.__name__,
"_fields": self._fields,
"values": self._asdict().values()
}
return New
def namedtuple_constructor(loader, node):
import IPython; IPython.embed()
value = loader.construct_scalar(node)
import re
pattern = re.compile(r'!!python/object/new:myapp.util\.')
yaml.add_implicit_resolver(u'!!myapp.util.namedtuple', pattern)
yaml.add_constructor(u'!!myapp.util.namedtuple', namedtuple_constructor)
假设这个代码在路径myapp/util.py的应用模块中。
不过,当我尝试加载时,我没有进入构造函数:
from myapp.util import namedtuple
x = namedtuple('test', ['a', 'b'])
t = x(1,2)
dump = yaml.dump(t)
load = yaml.load(dump)
它会找不到myapp.util中的New。
我尝试了各种其他方法,这只是我认为可能效果最好的一个。
免责声明:即使我进入了正确的构造函数,我也知道我的规范需要进一步完善,关于哪些参数被保存以及它们如何传递到生成的对象中,但对我来说,第一步是将YAML表示传入我的构造函数,之后的事情应该就简单了。
2 个回答
我希望能有一个方法,不用重新创建
namedtuple
函数,但这个方法达到了我的目标。
给你一个方案。
简而言之
这是一个使用 PyAML 3.12 的概念验证。
import yaml
def named_tuple(self, data):
if hasattr(data, '_asdict'):
return self.represent_dict(data._asdict())
return self.represent_list(data)
yaml.SafeDumper.yaml_multi_representers[tuple] = named_tuple
注意:为了保持代码的整洁,你应该使用你可以用到的 add_multi_representer()
方法,以及一个自定义的表示器/加载器,就像你之前做的那样。
这样你就能得到:
>>> import collections
>>> Foo = collections.namedtuple('Foo', 'x y z')
>>> yaml.safe_dump({'foo': Foo(1,2,3), 'bar':(4,5,6)})
'bar: [4, 5, 6]\nfoo: {x: 1, y: 2, z: 3}\n'
>>> print yaml.safe_dump({'foo': Foo(1,2,3), 'bar':(4,5,6)})
bar: [4, 5, 6]
foo: {x: 1, y: 2, z: 3}
这个是怎么工作的
正如你自己发现的,namedtuple
并没有一个特殊的类;探索它会得到:
>>> collections.namedtuple('Bar', '').mro()
[<class '__main__.Bar'>, <type 'tuple'>, <type 'object'>]
所以,Python 的命名元组实例其实是 tuple
的实例,只是多了一个 _asdict()
方法。
我解决了我的问题,不过方式有点不太理想。
现在我的应用程序使用了自己的 namedtuple
实现;我复制了 collections.namedtuple
的源代码,创建了一个基类,让所有新的 namedtuple
类型都可以继承,并且修改了模板(下面的摘录只是为了简洁,主要突出与原来的 namedtuple
源代码的不同之处)。
class namedtupleBase(tuple):
pass
_class_template = '''\
class {typename}(namedtupleBase):
'{typename}({arg_list})'
对 namedtuple
函数做了一个小改动,以便将新类添加到命名空间中:
namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
OrderedDict=OrderedDict, _property=property, _tuple=tuple,
namedtupleBase=namedtupleBase)
现在注册一个 multi_representer
就能解决问题了:
def repr_namedtuples(dumper, data):
return dumper.represent_mapping(u"!namedtupleBase", {
"__name__": data.__class__.__name__,
"__dict__": collections.OrderedDict(
[(k, v) for k, v in data._asdict().items()])
})
def consruct_namedtuples(loader, node):
value = loader.construct_mapping(node)
cls_ = namedtuple(value['__name__'], value['__dict__'].keys())
return cls_(*value['__dict__'].values())
yaml.add_multi_representer(namedtupleBase, repr_namedtuples)
yaml.add_constructor("!namedtupleBase", consruct_namedtuples)
感谢 在pyyaml中用相同基类表示不同类的实例 这个问题给了我灵感。
我希望能有一个不需要重新创建 namedtuple
函数的想法,但这个方法达到了我的目标。