13

我无法在 PyYAML 文档中找到如何执行此操作。我想表示我在 YAML 中定义的 python 类,并且如果在 YAML 中未指定参数,则为构造函数中的参数提供默认值。例如:

>>> 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'

我希望它会创建一个带有 的 Test 对象bar=3,但我猜它在创建对象时会绕过我的构造函数。如果我在 YAML 中包含 bar 的映射,一切都会按预期工作:

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

有谁知道我怎样才能让它使用默认值?

4

3 回答 3

12

我遇到了同样的问题:yaml_tag由于某种原因不起作用。所以我使用了替代方法:

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 }""")

输出:

[Test(foo=1, bar=3), Test(foo=10, bar=20)]
于 2011-08-29T07:32:28.373 回答
1

基于 alexanderlukanin13 的回答。这是我的剪辑。

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

然后你可以像这样使用它:

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
于 2017-05-17T18:46:31.220 回答
0

上面的答案效果很好,但这是一种使用基于类的方法使初始化完全工作的方法:

UNSPECIFIED = object()

class SomeYAMLObject(yaml.YAMLObject):
    @classmethod 
    def from_yaml(cls, loader, node):
        arg_spec = inspect.getfullargspec(cls.__init__)
        arg_spec.args.remove("self")

        arg_defaults = reversed(list(
                zip_longest(
                    reversed(arg_spec.args),
                    reversed(arg_spec.defaults or []),
                    fillvalue=UNSPECIFIED)))

        kwarg_defaults = reversed(list(
                    zip_longest(
                        reversed(arg_spec.kwonlyargs),
                        reversed(arg_spec.kwonlydefaults or []),
                        fillvalue=UNSPECIFIED)))

        node_mapping = loader.construct_mapping(node)
        used_nodes = set()

        # fill args first
        args = []
        for a,d in arg_defaults:
            if a in node_mapping:
                args.append(node_mapping[a])
                used_nodes.add(a)
            elif d is not UNSPECIFIED:
                args.append(d)
            else:
                raise Exception(f"Tag {cls.yaml_tag} is missing '{a}' argument")
        
        # then kwargs
        kwargs = {}
        for a,d in kwarg_defaults:
            if a in node_mapping:
                kwargs[a] = node_mapping[a]
                used_nodes.add(a)
            elif d is not UNSPECIFIED:
                args[a] = d
        
        # if it accepts additional kwargs, fill with leftover kwargs
        if arg_spec.varkw and len(used_nodes) != len(node_mapping):
            for k,v in node_mapping:
                if k not in used_nodes:
                    kwargs[k] = v

        return cls(*args,**kwargs)

它有点冗长,但如果缺少没有默认值的所需位置参数,则会给出一个很好的错误。

于 2022-02-10T20:03:48.580 回答