【问题标题】:Python parsing class from YAML来自 YAML 的 Python 解析类
【发布时间】:2018-09-02 07:59:12
【问题描述】:

我正在尝试输出,然后从 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]

为了写,我在类中添加了两个方法

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中添加了以下sn-p:

import yaml

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

现在,写作似乎工作正常,但阅读 YAML,代码

loader.construct_mapping(node)

似乎返回带有空数据的字典:

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

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


相关: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)

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

【问题讨论】:

  • 是否有特殊原因需要与 YAML 1.1 兼容并因此使用 PyYAML?
  • @Anthon 我需要使用 PyYAML,因为这是系统已经使用的解析器。你还有其他更现代的建议吗?我可能会考虑在某个时候升级系统

标签: python python-2.7 yaml pyyaml


【解决方案1】:

代替

nodeMap = loader.construct_mapping(node)

试试这个:

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

另外,您的 YAML 文件中有一个小错误:

!MyClass:

末尾的冒号不属于那里。

【讨论】:

  • 太棒了,就像一个魅力。我希望某个地方有人清楚地记录了deep 参数,这样我就不会在这里为这些问题打扰人们了:-(...非常感谢!
  • 是的,确实可以改进文档! pyyaml.org 不是由当前的 pyyaml 维护者维护的,我们希望将文档移至 github repo,也许我们也会找到时间来改进它。
【解决方案2】:

您的方法存在多个问题,甚至没有考虑到您应该阅读PEP 8, the style guide for Python code,尤其是Method Names and Instance Variables 上的部分

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

  2. 您使用dumper.represent_dict({data.YAMLTag : data.toDict()}) 将对象转储为键值对。你想要做的,至少如果你想在你的输出 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]
    

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

  3. 必须在two steps 中使用生成器(PyYAML 代码以正确的方式为您调用它)来构造一个可能是自引用(直接或间接)的复杂对象。在您的代码中,您假设您拥有创建MyClass 实例的所有参数。但是如果有自引用,这些参数必须包含该实例本身,并且它还没有创建。 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__(),但您应该按照here 的说明考虑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._ref 属性:

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

表示器和构造器“方法”:

    @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._refids 在加载后是相同的。

如果您在构造函数中使用简单的方法,则上面会引发错误,只需使用loader.construct_mapping(node, deep=True)

【讨论】:

  • 非常感谢,通过对如何使用不同功能的扩展评论获得更详细的答案确实很有帮助。你能告诉我为什么在from_yaml()yield 在对课程进行更新之前?还有,为什么yield而不是return(我明白如果你从执行中间返回,更新不会发生,但为什么不创建、更新、返回而不是创建、产生、更新)?
  • @gt6989b 答案中的两步链接有更多细节,但归结为递归性质。如果一个对象引用了它自己,你也许可以用一些技巧来更新。但是如果a 包括bb 然后包括cc 包括a 你必须传入所有这些半完整的“结构”。 yield 只是为了适应在 PyYAML 中创建这些结构的方法,它测试返回的对象是 GeneratorType,如果是,则在该对象上调用 .next()。这可以以不同的方式完成,但鉴于生成器是 Python 的一部分,这是一种很好的保留方式
  • 一切都在一个方法中
【解决方案3】:

考虑到上述答案,所有这些都很好,有一个 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,可在此处获得:

【讨论】:

    猜你喜欢
    • 2015-06-20
    • 1970-01-01
    • 2013-01-25
    • 2019-06-27
    • 2012-01-11
    • 1970-01-01
    • 2018-12-20
    • 1970-01-01
    • 2022-07-21
    相关资源
    最近更新 更多