Python类装饰器扩展类导致递归
我正在重写一个 ModelForm
的保存方法,但不知道为什么会导致递归:
@parsleyfy
class AccountForm(forms.ModelForm):
def save(self, *args, **kwargs):
# some other code...
return super(AccountForm, self).save(*args,**kwargs)
这导致了这个问题:
maximum recursion depth exceeded while calling a Python object
堆栈跟踪显示这一行不断地自我调用:
return super(AccountForm, self).save(*args,**kwargs)
现在,parsley 装饰器是这样的:
def parsleyfy(klass):
class ParsleyClass(klass):
# some code here to add more stuff to the class
return ParsleyClass
正如 @DanielRoseman 所建议的,Parsley 装饰器扩展了 AccountForm
,导致 super(AccountForm,self)
不断自我调用,那该怎么办呢?
我也搞不懂为什么这会导致递归。
2 个回答
这是我为让它正常工作所做的事情,我可以选择修改parsleyfy类,重写保存方法,像这样:
def parsleyfy(klass):
class ParsleyClass(klass):
def save(self, *args, **kwargs):
return super(klass, self).save(*args, **kwargs)
return ParsleyClass
或者把AccountForm的保存方法改成这样:
@parsleyfy
class AccountForm(forms.ModelForm):
def save(self, *args, **kwargs):
return super(forms.ModelForm, self).save(*args,**kwargs)
我不太明白的一个地方是 super(Class, self)
和 super(Parent, self)
之间的区别。我在这里问过这个 问题
你可以直接调用父类的方法:
@parsleyfy
class AccountForm(forms.ModelForm):
def save(self, *args, **kwargs):
# some other code...
return forms.ModelForm.save(self, *args,**kwargs)
这样可以巧妙地避免你在类装饰器中引入的问题。另一种选择是手动在一个不同名称的基类上调用装饰器,而不是使用 @
语法:
class AccountFormBase(forms.ModelForm):
def save(self, *args, **kwargs):
# some other code...
return super(AccountFormBase, self).save(*args,**kwargs)
AccountForm = parsleyfy(AccountFormBase)
不过,根据你想做的事情,你可能还想考虑使用一个 预保存信号,因为这通常是在Django中添加在模型保存过程之前应该执行的功能。
至于 为什么 会发生这种情况,想想代码被执行时发生了什么。
首先,声明了一个类。我们将这个原始类定义称为 Foo
,以便与后面装饰器创建的类区分开来。这个类有一个 save
方法,它会调用 super(AccountForm, self).save(...)
。
然后,这个类被传递给装饰器,装饰器定义了一个新类,我们称之为 Bar
,并且它继承自 Foo
。因此, Bar.save
等同于 Foo.save
- 它也会调用 super(AccountForm, self).save(...)
。这个第二个类随后从装饰器返回。
返回的类(Bar
)被赋值给名称 AccountForm
。
所以当你创建一个 AccountForm
对象时,其实是在创建一个 Bar
类型的对象。当你在它上面调用 .save(...)
时,它会查找 Bar.save
,而实际上 Bar.save
是 Foo.save
,因为它是从 Foo
继承来的,并且从未被重写。
正如我们之前提到的, Foo.save
调用了 super(AccountForm, self).save(...)
。问题在于,由于类装饰器的原因, AccountForm
不是 Foo
,而是 Bar
- 而 Bar
的父类是 Foo
。
所以当 Foo.save
查找 AccountForm
的父类时,它得到的是... Foo
。这意味着当它尝试在那个父类上调用 .save(...)
时,实际上它只是调用了自己,因此导致了无限递归。