pyyaml中的默认构造函数参数
我在PyYAML的文档里找不到怎么做这件事。我想把我定义的Python类用YAML表示出来,并且如果在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)
有没有人知道我怎么才能让它使用默认值呢?
3 个回答
1
上面的回答都很好,但这里有一种方法可以让初始化在基于类的方式下完全发挥作用:
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)
虽然这个方法有点长,但如果缺少必要的参数(没有默认值),它会给出一个很好的错误提示。
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
14
我遇到了同样的问题: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)]