cattrs 嵌套转换报 AttributeError: working_set
cattrs 旧版在 hook factory 内调用 include_subclasses,会覆盖外层代码生成的 working_set。正式 26.2.0 已修复状态保存,升级时还需保留原来的子类标签与字段规则。
cattrs 转换嵌套配置对象时,外层容器、内层容器和子类都能单独定义,组合后却在 make_dict_structure_fn() 结束处报:
AttributeError: working_set
若项目注册了自定义 structure_hook_factory,并在这个工厂里调用 include_subclasses() 配合 configure_tagged_union,需要检查旧版 cattrs 的代码生成状态。cattrs 24.1.2 中有明确的嵌套触发场景;它不是要求给业务对象增加一个名为 working_set 的字段。
内层子类策略清掉了外层仍在使用的集合
cattrs 生成嵌套转换函数时,用线程本地的 already_generating.working_set 记录正在生成的类型,以处理递归分派。外层容器生成过程中,会继续为成员类型寻找 hook,于是进入用户工厂和子类策略。
旧 include_subclasses 在为继承树生成处理函数时直接替换这份集合,结束后清空它,没有保存外层已经在用的状态。后续生成器可能认为自己是最外层调用并删除这个属性,真正的外层退出时再次清理,就触发 AttributeError。
因此只把报错行的 del 改成有条件删除,或者捕获 AttributeError 后继续,可能留下错误的递归分派与不完整 hook。这个内部状态不应由业务代码直接维护。
使用保存并恢复生成状态的正式实现
cattrs 26.2.0 已发布这项修复,当前 26.2.1 的子类策略会在进入相关生成步骤前保存原有集合,在完成后恢复。需要同时核对 attrs、项目自定义工厂和 Python 版本;26 系列要求 Python 3.10 或更高,旧 Python 环境不能只改一个版本号就继续安装。
升级时保留原来的 tag_name、标签生成规则、字段 rename、默认值和额外键策略。这些规则决定持久化配置的格式,不能为了消除生成异常而换掉业务标签,导致旧配置读成另一种类型。
如果暂时不能升级,可以评估把明确已知的子类策略配置移到 converter 初始化阶段,避免在嵌套 hook 生成中反复调用它。这个安排要求继承树在初始化时已完整,工厂与注册顺序也已核对;动态加载子类或工厂仍会递归配置同一类型时,不能把移出一行当作通用修复。
不要把一次 converter.structure() 的外层类型改成内部子类来绕过问题,那会改变调用方实际需要的结构。
验证要让外层生成器真正走完
保留能触发问题的完整容器层级:外层至少包含一个嵌套容器,里面既有带标签的父类字段,也有普通成员类型。分别核对每个标签是否构造出正确子类、成员值是否完整,以及整个外层转换能正常返回。
还要验证连续转换与序列化回读,防止第一次生成后的缓存掩盖注册顺序问题。对于缺失标签、未知标签和非法字段,仍应走原来的错误路径,不能因升级而默默创建默认对象。恢复旧依赖时一并恢复原配置和锁文件,不在已安装包中保留手工补丁。


