Python 从 YAML 解析 class

Python parsing class from YAML

我正在尝试输出然后从 YAML 解析回以下内容

import numpy as np
class MyClass(object):
    YAMLTag = '!MyClass'

    def __init__(self, name, times, zeros):
        self.name   = name
        self._T     = np.array(times)
        self._zeros = np.array(zeros)

YAML 文件看起来像

!MyClass:
  name: InstanceId
  times: [0.0, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0]
  zeros: [0.03, 0.03, 0.04, 0.03, 0.03, 0.02, 0.03]

要写,我已经添加到class两个方法

def toDict(self):
    return {'name'  : self.name,
            'times' : [float(t) for t in self._T],
            'zeros' : [float(t) for t in self._zeros]}
@staticmethod
def ToYAML(dumper, data):
    return dumper.represent_dict({data.YAMLTag : data.toDict()})

阅读方法

@staticmethod
def FromYAML(loader, node):
    nodeMap = loader.construct_mapping(node)
    return MyClass(name  = nodeMap['name'],
                   times = nodeMap['times'],
                   zeros = nodeMap['zeros'])

并在 YAML Documentation 之后,我在同一个 Python 文件中添加了以下代码段 myClass.py:

import yaml

yaml.add_constructor(MyClass.YAMLTag, MyClass.FromYAML)
yaml.add_representer(MyClass,         MyClass.ToYAML)

现在,写的好像没问题,但是读YAML,代码

loader.construct_mapping(node)

似乎return 字典有空数据:

{'zeros': [], 'name': 'InstanceId', 'times': []}

我应该如何修复 reader 才能正确执行此操作?或者也许我没有写出正确的东西?我花了很长时间查看 PyYAML 文档并通过包的实现方式进行调试,但无法找出解析复杂结构的方法,我似乎找到的唯一示例有一个 1 行 class 解析轻松出门。


相关:YAML parsing and Python


更新

手动解析节点如下:

name, times, zeros = None, None, None
for key, value in node.value:
    elementName = loader.construct_scalar(key)
    if elementName == 'name':
        name = loader.construct_scalar(value)
    elif elementName == 'times':
        times = loader.construct_sequence(value)
    elif elementName == 'zeros':
        zeros = loader.construct_sequence(value)
    else:
        raise ValueError('Unexpected YAML key %s' % elementName)

但问题仍然存在,有没有非手动的方法来做到这一点?

而不是

nodeMap = loader.construct_mapping(node)

试试这个:

nodeMap = loader.construct_mapping(node, deep=True)

另外,你的 YAML 文件有一点错误:

!MyClass:

末尾的冒号不属于那里。

您的方法存在多个问题,甚至没有考虑到您应该阅读 PEP 8, the style guide for Python code, in particular the part on Method Names and Instance Variables

  1. 正如您所说的那样,您已经查看了 Python 文档很长时间,您不可能没有注意到 yaml.load() 是不安全的。几乎从不需要使用它,如果您编写自己的表示器和构造器,则当然不需要。

  2. 您使用 dumper.represent_dict({data.YAMLTag : data.toDict()}) 将对象转储为 key-value 对。你想要做什么,至少如果你想在你的输出 YAML 中有一个标签是:dumper.represent_mapping(data.YAMLTag, data.toDict())。这将为您提供以下形式的输出:

    !MyClass
    name: InstanceId
    times: [0.0, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0]
    zeros: [0.03, 0.03, 0.04, 0.03, 0.03, 0.02, 0.03]
    

    即标记映射而不是 key-value 对,其中值是映射。 (而且我希望第一行是 '!MyClass': 以确保以感叹号开头的标量不会被解释为标记)。

  3. 构造一个复杂的对象,它可能 self-referential(直接或间接)必须在 中使用生成器完成(PyYAML 代码在正确的地方调用它适合你的方式)。在您的代码中,您假设您拥有创建 MyClass 实例的所有参数。但是如果有 self-reference,这些参数必须包括那个实例本身,而且它还没有被创建。 YAML 代码库中的正确示例代码是 construct_yaml_object() in constructor.py:

    def construct_yaml_object(self, node, cls):
        data = cls.__new__(cls)
        yield data
        if hasattr(data, '__setstate__'):
            state = self.construct_mapping(node, deep=True)
            data.__setstate__(state)
        else:
            state = self.construct_mapping(node)
            data.__dict__.update(state)
    

    您不必使用 .__new__(),但您应该按照

  4. 的说明考虑 deep=True

一般来说,__repr__() 也很有用,它允许您检查加载的对象,比 <__main__.MyClass object at 0x12345>

更具表现力

进口:

from __future__ import print_function

import sys
import yaml
from cStringIO import StringIO
import numpy as np

为了检查 self-referential 版本的正确工作,我将 self._ref 属性添加到 class:

class MyClass(object):
    YAMLTag = u'!MyClass'

    def __init__(self, name=None, times=[], zeros=[], ref=None):
        self.update(name, times, zeros, ref)

    def update(self, name, times, zeros, ref):
        self.name = name
        self._T = np.array(times)
        self._zeros = np.array(zeros)
        self._ref = ref

    def toDict(self):
        return dict(name=self.name,
                    times=self._T.tolist(),
                    zeros=self._zeros.tolist(),
                    ref=self._ref,
        )

    def __repr__(self):
        return "{}(name={}, times={}, zeros={})".format(
            self.__class__.__name__,
            self.name,
            self._T.tolist(),
            self._zeros.tolist(),
        )

    def update_self_ref(self, ref):
        self._ref = ref

表示者和构造者"methods":

    @staticmethod
    def to_yaml(dumper, data):
        return dumper.represent_mapping(data.YAMLTag, data.toDict())

    @staticmethod
    def from_yaml(loader, node):
        value = MyClass()
        yield value
        node_map = loader.construct_mapping(node, deep=True)
        value.update(**node_map)


yaml.add_representer(MyClass, MyClass.to_yaml, Dumper=yaml.SafeDumper)
yaml.add_constructor(MyClass.YAMLTag, MyClass.from_yaml, Loader=yaml.SafeLoader)

以及使用方法:

instance = MyClass('InstanceId',
                   [0.0, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0],
                   [0.03, 0.03, 0.04, 0.03, 0.03, 0.02, 0.03])
instance.update_self_ref(instance)

buf = StringIO()
yaml.safe_dump(instance, buf)

yaml_str = buf.getvalue()
print(yaml_str)


data = yaml.safe_load(yaml_str)
print(data)
print(id(data), id(data._ref))

以上组合给出:

&id001 !MyClass
name: InstanceId
ref: *id001
times: [0.0, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0]
zeros: [0.03, 0.03, 0.04, 0.03, 0.03, 0.02, 0.03]

MyClass(name=InstanceId, times=[0.0, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0], zeros=[0.03, 0.03, 0.04, 0.03, 0.03, 0.02, 0.03]) 
139737236881744 139737236881744

你可以看到datadata._refid加载后是一样的。

如果您在构造函数中使用简单的方法,仅使用 loader.construct_mapping(node, deep=True)

,以上内容会引发错误

考虑到上述答案,所有这些都很好,有一个 Python 包可用于从 YAML/JSON/dicts 巧妙地构造对象,并且正在积极开发和扩展。 (完全公开,我是这个包的合著者,参见here

安装:

pip install pickle-rick

使用:

定义 YAML 或 JSON 字符串(或文件)。

BASIC:
 text: test
 dictionary:
   one: 1
   two: 2
 number: 2
 list:
   - one
   - two
   - four
   - name: John
     age: 20
 USERNAME:
   type: env
   load: USERNAME
 callable_lambda:
   type: lambda
   load: "lambda: print('hell world!')"
 datenow:
   type: lambda
   import:
     - "from datetime import datetime as dd"
   load: "lambda: print(dd.utcnow().strftime('%Y-%m-%d'))"
 test_function:
   type: function
   name: test_function
   args:
     x: 7
     y: null
     s: hello world
     any:
       - 1
       - hello
   import:
     - "math"
   load: >
     def test(x, y, s, any):
       print(math.e)
       iii = 111
       print(iii)
       print(x,s)
       if y:
         print(type(y))
       else:
         print(y)
       for i in any:
         print(i)

然后把它当作一个对象来使用。

>> from pickle_rick import PickleRick

>> config = PickleRick('./config.yaml', deep=True, load_lambda=True)

>> config.BASIC.dictionary
{'one' : 1, 'two' : 2}

>> config.BASIC.callable_lambda()
hell world!

您可以定义 Python 函数,从其他文件或 REST API、环境变量加载额外数据,然后将所有内容写入 YAML 或再次 JSON。

这在构建需要结构化配置文件的系统或在笔记本中作为交互式结构时特别有效。

使用它有一个安全说明。只加载受信任的文件,因为任何代码都可以执行,因此避免在不知道完整内容的情况下只加载任何内容。

该包名为 PickleRick,可在此处获取: