我在 python 中遇到了问题。
我有一个自定义类 __getattr__
class ChoiceNumToName(object):
def __init__(self, django_choice_tuple):
self.ods_choice_tuple = django_choice_tuple
self.choice_data = {}
self.choice_point = -1
for choice_value, choice_name in django_choice_tuple:
self.choice_data.setdefault(choice_name, choice_value)
def __getattr__(self, item):
if item in self.choice_data:
return self.choice_data[item]
else:
raise AttributeError("no attribute %s" % item)
def __str__(self):
return str(self.ods_choice_tuple)
def __iter__(self):
self.choice_point = -1
return self
def __next__(self):
self.choice_point += 1
try:
return self.ods_choice_tuple[self.choice_point]
except IndexError:
raise StopIteration()
当我执行此操作时
a = ChoiceNumToName((
(1, "running"),
(2, "stopped"),
))
b = copy.deepcopy(a)
它引发 RecursionError: maximum recursion depth exceeded while calling a Python object
解决这个问题的例子,将__getattr__
函数改成这个
def __getattr__(self, item):
if item == "__setstate__":
raise AttributeError(item)
if item in self.choice_data:
return self.choice_data[item]
else:
raise AttributeError("no attribute %s" % item)
效果不错。
我从这里知道这个解决方案 https://github.com/python-babel/flask-babel/commit/8319a7f44f4a0b97298d20ad702f7618e6bdab6a
但谁能告诉我为什么?
最佳答案
TLDR:你的 __getattr__
在 choice_data
之前调用已被添加到实例字典中,这导致它无休止地递归。解决该问题的更好方法是立即为以 __
开头的任何属性引发 AttributeError捕捉任何其他特殊或内部属性。
发生这种情况是因为复制对象时 __init__
方法未被调用。相反,创建了一个新的空对象。这个新对象有一个空的 __dict__
. Python 的 pickle 协议(protocol)(也用于复制模块)有一个钩子(Hook) __setstate__
允许自定义应用状态(通常只是 __dict__
的内容,但是,例如,如果提供 __getstate__
,它可以是任何对象)。查看该 Hook 是否存在 hasattr(newobj, '__setstate__')
被称为 which,因为没有任何 __setstate__
在 MRO 或 __dict__
中导致你的__getattr__
被称为。你的__getattr__
然后尝试访问 self.choice_data
但是,正如我们之前提到的 __dict__
目前是空的。这会导致 __getattr__
再次调用方法以获取 choice_data
启动无限递归的属性。
特殊套管__setstate__
通过提早退出查找 __setstate__
来阻止递归被触发.当失败时,默认的复制机制生效,初始化新对象的 __dict__
。从国家。我心目中只有特殊外壳__setstate__
不是最好的解决方案。我认为最好立即为任何特殊或内部属性引发 AttributeError,即以 __
开头的那些,因为这可以防止其他奇怪情况的发生。另一种可能性是避免在 __getattr__
中使用属性查找。通过写作 self.__dict__['choice_data']
或 object.__getattribute__(self, 'choice_data')
.您还可以确保 choice_data
将通过实现 __new__
出现并将其分配给那里的对象。
关于python - python copy.deepcopy时的递归错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47299243/