我在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
但是有人可以告诉我为什么吗?
答案 0 :(得分:0)
方法__getstate__
和__setstate__
用于酸洗操作。为什么这很重要?来自Python docs on copying:
类可以使用相同的接口来控制用于控制酸洗的复制。
通过定义引用自身的__setstate__
,您创建了一个递归对象,因此出现了RecursionError。
答案 1 :(得分:0)
TLDR:在__getattr__
添加到实例字典之前调用choice_data
,导致它无休止地递归。解决该问题的更好方法是立即针对以__
开头的任何属性引发AttributeError,以捕获任何其他特殊或内部属性。
这是因为复制对象时未调用__init__
方法。而是创建一个新的空对象。这个新对象有一个空__dict__
。 Python的pickle协议(也用于复制模块)有一个钩子__setstate__
,允许自定义应用状态(通常只是__dict__
的内容,但是,例如{{1}提供,它可以是任何对象)。要查看该挂钩是否存在,__getstate__
会被调用,因为MRO中没有任何hasattr(newobj, '__setstate__')
,__setstate__
也不会导致__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
出现。