Default constructor parameters in pyyaml

2019-02-09 01:40发布

I haven't been able to find out how to do this in the PyYAML documentation. I want to represent python classes I've defined in YAML, and have a default value given to a parameter in the constructor if it's not specified in the YAML. For example:

>>> class Test(yaml.YAMLObject):
...     yaml_tag = u"!Test"
...     def __init__(self, foo, bar=3):
...             self.foo = foo
...             self.bar = bar
...     def __repr__(self):
...             return "%s(foo=%r, bar=%r)" % (self.__class__.__name__, self.foo, self.bar)
... 
>>> yaml.load("""
... --- !Test
... foo: 5
... """)
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 7, in __repr__
AttributeError: 'Test' object has no attribute 'bar'

I expected that it would create a Test object with bar=3, but I guess it bypasses my constructor when it creates the object. If I include a mapping for bar in the YAML, everything works as expected:

>>> yaml.load("""
... --- !Test
... foo: 5
... bar: 42
... """)
Test(foo=5, bar=42)

Does anyone know how I can have it use a default value?

2条回答
一夜七次
2楼-- · 2019-02-09 02:05

Based on alexanderlukanin13's answer. Here's my cut.

import yaml

YAMLObjectTypeRegistry = {}

def register_type(target):
    if target.__name__ in YAMLObjectTypeRegistry:
        print "{0} already in registry.".format(target.__name__)
    elif 'yaml_tag' not in target.__dict__.keys():
        print target.__dict__
        raise TypeError("{0} must have yaml_tag attribute".format(
            target.__name__))
    elif target.__dict__['yaml_tag'] is None:
        pass
    else:
        YAMLObjectTypeRegistry[target.__name__] = target
        yaml.add_constructor(
                target.__dict__['yaml_tag'],
                lambda loader, node: target(**loader.construct_mapping(node)))
        print "{0} added to registry.".format(target.__name__)

class RegisteredYAMLObjectType(type):
    def __new__(meta, name, bases, class_dict):
        cls = type.__new__(meta, name, bases, class_dict)
        register_type(cls)
        return cls

class RegisteredYAMLObject(object):
    __metaclass__=RegisteredYAMLObjectType
    yaml_tag = None

You can then use it like this:

class MyType(registry.RegisteredYAMLObject):
    yaml_tag = u'!mytype'
    def __init__(self, name, attr1='default1', attr2='default2'):
        super(MyType, self).__init__()
        self.name = name
        self.attr1 = attr1
        self.attr2 = attr2
查看更多
淡お忘
3楼-- · 2019-02-09 02:18

I encountered the same problem: yaml_tag doesn't work for some reason. So I used alternative approach:

import yaml

def constructor(loader, node) :
    fields = loader.construct_mapping(node)
    return Test(**fields)

yaml.add_constructor('!Test', constructor)

class Test(object) :
    def __init__(self, foo, bar=3) :
        self.foo = foo
        self.bar = bar
    def __repr__(self):
        return "%s(foo=%r, bar=%r)" % (self.__class__.__name__, self.foo, self.bar)

print yaml.load("""
- !Test { foo: 1 }
- !Test { foo: 10, bar: 20 }""")

Output:

[Test(foo=1, bar=3), Test(foo=10, bar=20)]
查看更多
登录 后发表回答