覆盖 from_yaml 以添加自定义 YAML 标签

Overriding from_yaml to add custom YAML tag

覆盖 from_yaml 是否足以从 class 注册标签,或者是否有必要使用 yaml.add_constructor(Class.yaml_tag, Class.from_yaml)?如果我不使用 te add_constructor 方法,我的 YAML 标签将无法识别。我所拥有的示例:

import yaml

class Something(yaml.YAMLObject):

    yaml_tag = u'!Something'

    @classmethod
    def from_yaml(cls,loader,node):
        # Set attributes to None if not in file
        values = loader.construct_mapping(node, deep=True)
        attr = ['attr1','attr2']
        result = {}
        for val in attr:
            try:
                result[val] = values[val]
            except KeyError:
                result[val] = None
        return cls(**result)

这足以让它工作吗?我对使用 from_yaml 与使用我上面提到的方法注册的任何其他构造函数感到困惑。我想我缺少一些基本的东西,因为他们说:

Subclassing YAMLObject is an easy way to define tags, constructors, and representers for your classes. You only need to override the yaml_tag attribute. If you want to define your custom constructor and representer, redefine the from_yaml and to_yaml method correspondingly.

确实不需要显式注册:

import yaml

class Something(yaml.YAMLObject):
    yaml_tag = u'!Something'

    def __init__(self, *args, **kw):
        print('some_init', args, kw)

    @classmethod
    def from_yaml(cls,loader,node):
        # Set attributes to None if not in file
        values = loader.construct_mapping(node, deep=True)
        attr = ['attr1','attr2']
        result = {}
        for val in attr:
            try:
                result[val] = values[val]
            except KeyError:
                result[val] = None
        return cls(**result)

yaml_str = """\
test: !Something
   attr1: 1
   attr2: 2
"""

d = yaml.load(yaml_str)

给出:

some_init () {'attr1': 1, 'attr2': 2}

但是根本不需要使用 PyYAML 的 load() 这是 记录为不安全。如果设置 yaml_loader class 属性,则可以只使用 safe_load

import yaml

class Something(yaml.YAMLObject):
    yaml_tag = u'!Something'

    yaml_loader = yaml.SafeLoader

    def __init__(self, *args, **kw):
        print('some_init', args, kw)

    @classmethod
    def from_yaml(cls,loader,node):
        # Set attributes to None if not in file
        values = loader.construct_mapping(node, deep=True)
        attr = ['attr1','attr2']
        result = {}
        for val in attr:
            try:
                result[val] = values[val]
            except KeyError:
                result[val] = None
        return cls(**result)

yaml_str = """\
test: !Something
   attr1: 1
   attr2: 2
"""

d = yaml.safe_load(yaml_str)

因为这给出了相同的结果:

some_init () {'attr1': 1, 'attr2': 2}

(使用 Python 3.6 和 Python 2.7 完成)

注册是在 yaml.YAMLObject 的 metaclass 的 __init__() 中完成的:

class YAMLObjectMetaclass(type):
    """
    The metaclass for YAMLObject.
    """
    def __init__(cls, name, bases, kwds):
        super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
        if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
            cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
            cls.yaml_dumper.add_representer(cls, cls.to_yaml)

因此,您可能以某种方式干扰了完整 class 定义中的初始化。尝试像我一样从一个最小的实现开始,然后在你的 class 上添加你需要的功能,直到出现问题。