通过PyYAML序列化namedtuples

时间:2014-07-12 22:07:37

标签: python yaml pyyaml namedtuple

我正在寻找一些使用PyYAML在YAML中序列化namedtuples的合理方法。

我不想做的一些事情:

  • 依靠动态调用在实例化namedtuple时添加构造函数/表示符/解析器。这些YAML文件可以在以后存储和重新加载,因此我不能依赖它们在恢复时存在的相同运行时环境。

  • 在全局注册namedtuples。

  • 依靠具有唯一名称的命名元组

我正在考虑这些问题:

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 个答案:

答案 0 :(得分:1)

我能够解决我的问题,但方式略逊于理想。

我的应用程序现在使用自己的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)

Hattip向Represent instance of different classes with the same base class in pyyaml寻求解决方案的灵感。

会喜欢一个不需要重新创建namedtuple函数的想法,但这完成了我的目标。

答案 1 :(得分:0)

  

会喜欢一个不需要重新创建namedtuple函数的想法,但这可以实现我的目标。

你在这里。

TL; DR

使用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()实例。