IMO 你没有滥用图书馆,只是解决了它目前的缺点/不完整。
在ruamel.yaml
获得带有YAML()
实例的 API 之前,它具有 PyYAML 的基于函数的 API 以及一些扩展,并且其他 PyYAML 的问题必须以类似的不自然方式解决。例如,我恢复为拥有可以调用(使用
__call__()
)实例的类,然后可以将其方法更改为只能访问从文档解析的 YAML 文档版本(因为 ruamel.yaml 支持 YAML 1.2 和 1.1,而 PyYAML 仅(部分)支持1.1)。
但在 ruamel.yaml 的YAML()
实例下,并非所有情况都得到了改善。从 PyYAML 继承的代码将类属性中各种构造函数的信息存储为查找表(在yaml_constructor
resp
上yaml_multi_constructor
),并且 ruamel.yaml 仍然这样做(因为完整的旧 PyYAML-escque API 实际上仍然存在,并且仅使用版本 0.17已收到未来弃用警告)。
到目前为止,您的方法很有趣,因为您这样做:
loader.constructor.add_multi_constructor("", construct_node)
代替:
loader.Constructor.add_multi_constructor("", construct_node)
(您可能知道这是一个必要时loader.constructor
实例化的属性
,但此答案的其他读者可能不会)loader.Constructor
甚至:
def get_loader(parameter):
def construct_node(constructor: ruamel.yaml.Constructor, tag: str, node: ruamel.yaml.Node):
return parameter(tag.lstrip("!"), str(node.value))
# Create a new class to prevent state sharing through class attributes.
class ConstructorWrapper(ruamel.yaml.constructor.RoundTripConstructor):
pass
ConstructorWrapper.add_multi_constructor("", construct_node)
loader = ruamel.yaml.YAML()
loader.Constructor = ConstructorWrapper
return loader
您的代码有效,是因为构造函数与.add_multi_constructor()
类方法一样存储在类属性中。
因此,从 API 损坏的角度来看,您所做的并不完全安全。ruamel.yaml 还不是 1.0 版,可能会破坏您的代码的 (API) 更改可能会伴随任何次要版本号更改。您应该为您的生产代码(例如ruamel.yaml<0.18
)相应地设置您的版本依赖项,并且仅在使用具有新的次要版本号的 ruamel.yaml 版本进行测试后更新该次要编号。
add_constructor()
可以通过更新类方法和add_multi_constructor()
“正常”方法来透明地更改类属性的使用,并在__init__()
. 您调用实例的两个示例:
loader.constructor.add_multi_constructor("", construct_node)
会得到想要的结果,但是 ruamel.yaml 的行为在调用类时不会改变add_multi_constructor
:
loader.Constructor.add_multi_constructor("", construct_node)
然而,更改类方法add_constructor()
并add_multi_constructor()
以这种方式影响所有代码,这恰好提供了一个实例而不是类(并且所述代码对结果很好)。
更有可能的是,两个新的实例方法将被添加到Constructor
类和YAML()
实例中,并且在弃用期之后,类方法将被逐步淘汰或更改以检查类而不是传入的实例带有警告(全局函数add_constructor()
和add_multi_constructor()
从 PyYAML 继承)。
除了将您的生产代码固定在次要版本号之外,主要建议是确保您的测试代码显示
PendingDeprecationWarning
. 如果您使用的pytest
是默认情况下是这种情况。这应该给你足够的时间来调整你的代码以适应警告的建议。
如果 ruamel.yaml 的作者不再懒惰,他可能会为此类 API 添加/更改提供一些文档。
import ruamel.yaml
import types
import inspect
class MyConstructor(ruamel.yaml.constructor.RoundTripConstructor):
_cls_yaml_constructors = {}
_cls_yaml_multi_constructors = {}
def __init__(self, *args, **kw):
self._yaml_constructors = {
'tag:yaml.org,2002:null': self.__class__.construct_yaml_null,
'tag:yaml.org,2002:bool': self.__class__.construct_yaml_bool,
'tag:yaml.org,2002:int': self.__class__.construct_yaml_int,
'tag:yaml.org,2002:float': self.__class__.construct_yaml_float,
'tag:yaml.org,2002:binary': self.__class__.construct_yaml_binary,
'tag:yaml.org,2002:timestamp': self.__class__.construct_yaml_timestamp,
'tag:yaml.org,2002:omap': self.__class__.construct_yaml_omap,
'tag:yaml.org,2002:pairs': self.__class__.construct_yaml_pairs,
'tag:yaml.org,2002:set': self.__class__.construct_yaml_set,
'tag:yaml.org,2002:str': self.__class__.construct_yaml_str,
'tag:yaml.org,2002:seq': self.__class__.construct_yaml_seq,
'tag:yaml.org,2002:map': self.__class__.construct_yaml_map,
None: self.__class__.construct_undefined
}
self._yaml_constructors.update(self._cls_yaml_constructors)
self._yaml_multi_constructors = self._cls_yaml_multi_constructors.copy()
super().__init__(*args, **kw)
def construct_non_recursive_object(self, node, tag=None):
# type: (Any, Optional[str]) -> Any
constructor = None # type: Any
tag_suffix = None
if tag is None:
tag = node.tag
if tag in self._yaml_constructors:
constructor = self._yaml_constructors[tag]
else:
for tag_prefix in self._yaml_multi_constructors:
if tag.startswith(tag_prefix):
tag_suffix = tag[len(tag_prefix) :]
constructor = self._yaml_multi_constructors[tag_prefix]
break
else:
if None in self._yaml_multi_constructors:
tag_suffix = tag
constructor = self._yaml_multi_constructors[None]
elif None in self._yaml_constructors:
constructor = self._yaml_constructors[None]
elif isinstance(node, ScalarNode):
constructor = self.__class__.construct_scalar
elif isinstance(node, SequenceNode):
constructor = self.__class__.construct_sequence
elif isinstance(node, MappingNode):
constructor = self.__class__.construct_mapping
if tag_suffix is None:
data = constructor(self, node)
else:
data = constructor(self, tag_suffix, node)
if isinstance(data, types.GeneratorType):
generator = data
data = next(generator)
if self.deep_construct:
for _dummy in generator:
pass
else:
self.state_generators.append(generator)
return data
def get_args(*args, **kw):
if kw:
raise NotImplementedError('can currently only handle positional arguments')
if len(args) == 2:
return MyConstructor, args[0], args[1]
else:
return args[0], args[1], args[2]
def add_constructor(self, tag, constructor):
self, tag, constructor = MyConstructor.get_args(*args, **kw)
if inspect.isclass(self):
self._cls_yaml_constructors[tag] = constructor
return
self._yaml_constructors[tag] = constructor
def add_multi_constructor(*args, **kw): # self, tag_prefix, multi_constructor):
self, tag_prefix, multi_constructor = MyConstructor.get_args(*args, **kw)
if inspect.isclass(self):
self._cls_yaml_multi_constructors[tag_prefix] = multi_constructor
return
self._yaml_multi_constructors[tag_prefix] = multi_constructor
def get_loader_org(parameter):
def construct_node(constructor: ruamel.yaml.Constructor, tag: str, node: ruamel.yaml.Node):
return parameter(tag.lstrip("!"), str(node.value))
loader = ruamel.yaml.YAML()
loader.Constructor = MyConstructor
loader.constructor.add_multi_constructor("", construct_node)
return loader
foo = get_loader_org(lambda tag, node: f"foo: {tag}, {node}")
bar = get_loader_org(lambda tag, node: f"bar: {tag}, {node}")
print('>org<', foo.load("!abc 123"), bar.load("!xyz 456"), sep="\n")
def get_loader_instance(parameter):
def construct_node(constructor: ruamel.yaml.Constructor, tag: str, node: ruamel.yaml.Node):
return parameter(tag.lstrip("!"), str(node.value))
# Create a new class to prevent state sharing through class attributes.
class ConstructorWrapper(MyConstructor):
pass
loader = ruamel.yaml.YAML()
loader.Constructor = ConstructorWrapper
loader.constructor.add_multi_constructor("", construct_node)
return loader
foo = get_loader_instance(lambda tag, node: f"foo: {tag}, {node}")
bar = get_loader_instance(lambda tag, node: f"bar: {tag}, {node}")
print('>instance<', foo.load("!abc 123"), bar.load("!xyz 456"), sep="\n")
def get_loader_cls(parameter):
def construct_node(constructor: ruamel.yaml.Constructor, tag: str, node: ruamel.yaml.Node):
return parameter(tag.lstrip("!"), str(node.value))
# Create a new class to prevent state sharing through class attributes.
class ConstructorWrapper(MyConstructor):
pass
loader = ruamel.yaml.YAML()
loader.Constructor = ConstructorWrapper
loader.Constructor.add_multi_constructor("", construct_node)
# ^ using the virtual class method
return loader
foo = get_loader_cls(lambda tag, node: f"foo: {tag}, {node}")
bar = get_loader_cls(lambda tag, node: f"bar: {tag}, {node}")
print('>cls<', foo.load("!abc 123"), bar.load("!xyz 456"), sep="\n")
这使:
>org<
foo: abc, 123
bar: xyz, 456
>instance<
foo: abc, 123
bar: xyz, 456
>cls<
bar: abc, 123
bar: xyz, 456