PyYaml加载含嵌套类的字符串失败,如何修复类路径问题?
解决PyYAML序列化/反序列化嵌套类的问题
你遇到的问题确实很典型:PyYAML默认处理嵌套类时,只会用类的短名称(__name__)生成YAML标签,而不是包含外层类的完整限定名(__qualname__),导致反序列化时找不到嵌套类的定义。
核心原因
PyYAML序列化对象时,默认生成的标签是!!python/object:{module}.{class.__name__},但嵌套类的__name__只有NestedChild,而它的实际完整路径应该是NotWorkingParent.NestedChild,对应标签应该是!!python/object:__main__.NotWorkingParent.NestedChild。反序列化时,PyYAML在__main__模块下找NestedChild,自然找不到,因为它是NotWorkingParent的内部类。
通用解决方案:自定义代表器和构造器
下面是一个通用的解决方法,通过自定义PyYAML的代表器(负责序列化)和构造器(负责反序列化),让它正确处理嵌套类的完整路径:
import yaml from yaml.representer import Representer from yaml.constructor import Constructor class NotWorkingParent: class NestedChild: def __init__(self): self.nc1 = 11 self.nc2 = 22 def __init__(self): self.p1 = 11 self.p2 = 22 self.nc = NotWorkingParent.NestedChild() # 自定义代表器:用__qualname__生成完整类路径的标签 def nested_class_representer(dumper, obj): # 获取类的完整限定名(包含外层类) full_class_path = f"{obj.__class__.__module__}.{obj.__class__.__qualname__}" return dumper.represent_mapping(f"!!python/object:{full_class_path}", obj.__dict__) # 自定义构造器:根据完整类路径找到嵌套类并实例化 def nested_class_constructor(loader, node): # 从标签中提取完整类路径 full_class_path = node.tag.split(':')[-1] # 拆分模块名和类的限定名 module_name, class_qualname = full_class_path.rsplit('.', 1) # 导入模块 module = __import__(module_name, fromlist=['']) # 逐层获取嵌套类 target_class = module for part in class_qualname.split('.'): target_class = getattr(target_class, part) # 创建实例并加载属性 instance = target_class.__new__(target_class) loader.construct_mapping(node, deep=True) instance.__dict__.update(node.value) return instance # 注册自定义逻辑到PyYAML yaml.add_representer(object, nested_class_representer) yaml.add_constructor('!!python/object:', nested_class_constructor) # 测试序列化与反序列化 parent = NotWorkingParent() yaml_parent_str = yaml.dump(parent) print("**** yaml_parent_str ***") print(yaml_parent_str) print("**** parent_from_yaml_str ***") # 注意:使用FullLoader避免安全警告 parent_from_yaml_str = yaml.load(yaml_parent_str, Loader=yaml.FullLoader) print(parent_from_yaml_str) print(f"嵌套类实例类型:{type(parent_from_yaml_str.nc)}") print(f"嵌套类属性nc1的值:{parent_from_yaml_str.nc.nc1}")
代码解释
- 代表器逻辑:利用类的
__qualname__属性(Python 3.3+支持)获取嵌套类的完整路径,生成正确的YAML标签,确保序列化后的内容包含NotWorkingParent.NestedChild的完整路径。 - 构造器逻辑:解析标签中的完整类路径,逐层遍历模块和外层类,最终找到嵌套类的定义,然后创建实例并加载属性。
- 注册逻辑:将自定义的代表器和构造器绑定到PyYAML,让所有对象的序列化/反序列化都使用这套逻辑,适合处理任意嵌套类。
运行结果
执行上述代码后,你会看到序列化后的YAML标签已经是完整路径:
**** yaml_parent_str *** !!python/object:__main__.NotWorkingParent nc: !!python/object:__main__.NotWorkingParent.NestedChild {nc1: 11, nc2: 22} p1: 11 p2: 22 **** parent_from_yaml_str *** <__main__.NotWorkingParent object at 0x000001F8D7A205E0> 嵌套类实例类型:<class '__main__.NotWorkingParent.NestedChild'> 嵌套类属性nc1的值:11
针对特定嵌套类的简化方案
如果你只需要处理某个特定的嵌套类,也可以手动注册该类的序列化/反序列化逻辑,代码更简洁:
import yaml class NotWorkingParent: class NestedChild: def __init__(self): self.nc1 = 11 self.nc2 = 22 def __init__(self): self.p1 = 11 self.p2 = 22 self.nc = NotWorkingParent.NestedChild() # 手动注册NestedChild的代表器 yaml.add_representer(NotWorkingParent.NestedChild, lambda dumper, obj: dumper.represent_mapping( f"!!python/object:{__name__}.NotWorkingParent.NestedChild", obj.__dict__ ) ) # 手动注册NestedChild的构造器 yaml.add_constructor( f"!!python/object:{__name__}.NotWorkingParent.NestedChild", lambda loader, node: loader.construct_yaml_object(node, NotWorkingParent.NestedChild) ) # 测试 parent = NotWorkingParent() yaml_parent_str = yaml.dump(parent) parent_from_yaml_str = yaml.load(yaml_parent_str, Loader=yaml.FullLoader) print(parent_from_yaml_str) print(type(parent_from_yaml_str.nc))
这种方法不需要通用逻辑,适合只处理少量特定嵌套类的场景。
内容的提问来源于stack exchange,提问作者Praveen Kulkarni
相关产品推荐
相关产品推荐

