如何让ruamel.yaml识别NamedTuple为tuple以避免序列化报错?
问题:ruamel.yaml无法正确序列化NamedTuple子类Loc
以下是测试代码:
import sys from typing import NamedTuple import ruamel.yaml as ryaml class Loc(NamedTuple): lat: float long: float data = { "APAC": { "rating": 5, "leads": ["Jane", "John"], "locs": [Loc(1.0, 1.0), Loc(2.0, 2.0)], }, "EMEA": { "rating": 5, "leads": ["Jane", "Jack"], "locs": [Loc(3.0, 3.0), Loc(4.0, 4.0)], } } def main(): # 验证Loc实例确实是tuple子类 assert all(map(lambda o: isinstance(o, tuple), data["APAC"]["locs"])) assert all(map(lambda o: isinstance(o, tuple), data["EMEA"]["locs"])) yml = ryaml.YAML() yml.register_class(Loc) yml.dump(data, sys.stdout) if __name__ == '__main__': main()
执行后抛出如下错误:
File "C:\Repos\@Venv\myproj-cpy3.11-1\Lib\site-packages\ruamel\yaml\representer.py", line 1090, in represent_yaml_object anchor = state.pop(Anchor.attrib, None) ^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'pop'
直接使用原生tuple替代Loc()则无错误,环境为CPython 3.11 + ruamel.yaml==0.17.21。
解决方案
方法一:自定义Representer,将Loc实例转为tuple序列化
不需要调用yml.register_class(Loc),而是给Loc类添加自定义的序列化逻辑,让ruamel.yaml按tuple规则处理:
修改后的main函数:
def main(): assert all(map(lambda o: isinstance(o, tuple), data["APAC"]["locs"])) assert all(map(lambda o: isinstance(o, tuple), data["EMEA"]["locs"])) yml = ryaml.YAML() # 自定义Loc的序列化逻辑,转为tuple处理 def represent_loc(dumper, loc_instance): return dumper.represent_tuple(tuple(loc_instance)) yml.representer.add_representer(Loc, represent_loc) yml.dump(data, sys.stdout)
方法二:给Loc类指定yaml_tag为tuple类型
直接在Loc类中添加yaml_tag属性,指定为!!python/tuple,ruamel.yaml会自动用tuple的序列化规则处理:
修改后的Loc类:
class Loc(NamedTuple): lat: float long: float yaml_tag = '!!python/tuple'
然后main函数中去掉yml.register_class(Loc)即可:
def main(): assert all(map(lambda o: isinstance(o, tuple), data["APAC"]["locs"])) assert all(map(lambda o: isinstance(o, tuple), data["EMEA"]["locs"])) yml = ryaml.YAML() yml.dump(data, sys.stdout)
内容的提问来源于stack exchange,提问作者pepoluan
相关产品推荐
相关产品推荐

