Python类装饰器扩展类导致递归

5 投票
2 回答
1490 浏览
提问于 2025-04-17 15:01

我正在重写一个 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 个回答

0

这是我为让它正常工作所做的事情,我可以选择修改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) 之间的区别。我在这里问过这个 问题

6

你可以直接调用父类的方法:

@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.saveFoo.save,因为它是从 Foo 继承来的,并且从未被重写。

正如我们之前提到的, Foo.save 调用了 super(AccountForm, self).save(...)问题在于,由于类装饰器的原因, AccountForm 不是 Foo,而是 Bar - 而 Bar 的父类是 Foo

所以当 Foo.save 查找 AccountForm 的父类时,它得到的是... Foo。这意味着当它尝试在那个父类上调用 .save(...) 时,实际上它只是调用了自己,因此导致了无限递归。

撰写回答