为什么重写__getattr__或__getattribute__会导致deepcopy调用失败,如何解决?
报错原因
copy.deepcopy复制自定义类实例的流程中,会优先尝试读取实例的__dict__、__getstate__、__deepcopy__等特殊魔法属性来获取实例状态。你重写的__getattr__会在属性常规查找失败时触发,栈溢出的根因是无限递归:
- deepcopy创建完新的空实例后,还未完成
self.data属性的初始化,此时访问任何属性都会触发__getattr__ __getattr__的逻辑需要先访问self.data判断key是否存在,但此时self.data还没创建,常规查找失败,又会再次触发__getattr__,循环往复直到递归深度超出上限。
如果是重写__getattribute__触发的问题,原理更直接:__getattribute__会拦截所有属性访问请求,只要方法内部访问实例属性时没有通过super().__getattribute__调用父类原生逻辑,就会直接触发无限递归,哪怕是普通属性访问也会报错,和deepcopy没有必然关联。
修复方案
有两种常用的修复方式,可根据业务场景选择:
方案1:过滤魔法属性,不拦截特殊属性查找
修改__getattr__的逻辑,遇到双下划线开头和结尾的魔法属性直接抛出AttributeError,交给Python原生的属性查找逻辑处理,不走自定义的data字典判断。
class A: def __init__(self): self.data = {'key': 'value'} def __getattr__(self, key): # 放过所有魔法属性 if key.startswith('__') and key.endswith('__'): raise AttributeError if key not in self.data: raise AttributeError return self.data[key]
该方案改造成本极低,适合只需要自定义普通属性查找逻辑的场景。
方案2:自定义__deepcopy__方法,主动控制复制逻辑
自己实现实例的深拷贝逻辑,避免deepcopy默认流程触发你的自定义属性查找规则。
import copy class A: def __init__(self): self.data = {'key': 'value'} def __getattr__(self, key): if key not in self.data: raise AttributeError return self.data[key] def __deepcopy__(self, memo): new_obj = A() # 仅深拷贝自定义的data属性即可 new_obj.data = copy.deepcopy(self.data, memo) # 将新实例加入memo字典,避免循环引用场景下的重复复制 memo[id(self)] = new_obj return new_obj
该方案灵活度更高,适合实例复制有特殊业务要求的场景。
内容的提问来源于stack exchange,提问作者Roy_Kid
相关产品推荐
相关产品推荐

