首页 > 解决方案 > 如何在 PyYaml 中处理标签/类的向后兼容性?

问题描述

假设我有一个类MyClass驻留在 package 中my_package。将此类数据转储到 YAML 可能会产生:

!!python/object:my_package.MyClass
my_field_1: "foo"
my_field_2: "bar"

这些数据可以用默认加载器反序列化就好了。但是我们如何处理重构包/类名呢?例如,如果我们将包重命名为my_new_package,则代码无法像预期的那样反序列化现有的 YAML 文件:

yaml.constructor.ConstructorError: while constructing a Python object
cannot find 'MyClass' in the module 'my_package'

如何向 YAML 加载程序添加向后兼容性,以便仍然可以加载旧数据?


我的第一次尝试是自定义加载器并注册旧标签名称以实现向后兼容性:

class CustomLoader(yaml.SafeLoader):
    pass

def my_class_loader(loader, node):
    # to be implemented

CustomLoader.add_constructor("!!python/object:my_package.MyClass", my_class_loader)

data = yaml.load(f, Loader=CustomLoader)

不幸的是,PyYaml 从未调用此自定义加载。是否有另一种方法可以将旧标签注入到加载过程中?

标签: pythonpyyaml

解决方案


我找到了一个可行的解决方案,但我不确定这是否是解决它的最优雅的方法:

看起来 PyYaml在执行构造函数查找之前在内部将存储的标签!!python/object:my_package.MyClass转换为。tag:yaml.org,2002:python/object:my_package.MyClass

因此,为了为旧标签名称注册一个自定义加载器,可以执行以下操作:

# Example class
class MyClass(object):
    def __init__(my_field_1, my_field_2):
        self.my_field_1 = my_field_1
        self.my_field_2 = my_field_2

    @staticmethod
    def from_dict(d):
        return MyClass(
            my_field_1=d["my_field_1"],
            my_field_2=d["my_field_2"],
        )

# Build a custom loader
class CustomLoader(yaml.SafeLoader):
    pass

def my_class_loader(loader, node):
    mapping = loader.construct_mapping(node)
    return MyClass.from_dict(mapping)

# Register the old tag name
CustomLoader.add_constructor(
    "tag:yaml.org,2002:python/object:my_package.MyClass", 
    my_class_loader)

# Load using custom loader
data = yaml.load(f, Loader=CustomLoader)

推荐阅读