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

Jam*_*Lin 5 python forms django recursion save

我正在覆盖a的save方法,ModelForm我不知道它为什么会导致递归:

@parsleyfy
class AccountForm(forms.ModelForm):
    def save(self, *args, **kwargs):
        # some other code...
        return super(AccountForm, self).save(*args,**kwargs)
Run Code Online (Sandbox Code Playgroud)

导致这个:

maximum recursion depth exceeded while calling a Python object
Run Code Online (Sandbox Code Playgroud)

Stacktrace显示此行反复调用自身:

return super(AccountForm, self).save(*args,**kwargs) 
Run Code Online (Sandbox Code Playgroud)

现在,欧芹装饰器是这样的:

def parsleyfy(klass):
    class ParsleyClass(klass):
      # some code here to add more stuff to the class
    return ParsleyClass
Run Code Online (Sandbox Code Playgroud)

正如@DanielRoseman所说的那样,Parsley装饰器扩展了AccountForm导致它super(AccountForm,self)不断调用自己,解决方案是什么?

此外,我无法理解为什么这会导致递归.

Amb*_*ber 6

你能做的就是直接调用父方法:

@parsleyfy
class AccountForm(forms.ModelForm):
    def save(self, *args, **kwargs):
        # some other code...
        return forms.ModelForm.save(self, *args,**kwargs)
Run Code Online (Sandbox Code Playgroud)

这应该整齐地避免您的类装饰器引入的问题.另一种选择是在不同命名的基类上手动调用装饰器,而不是使用@语法:

class AccountFormBase(forms.ModelForm):
    def save(self, *args, **kwargs):
        # some other code...
        return super(AccountFormBase, self).save(*args,**kwargs)

AccountForm = parsleyfy(AccountFormBase)
Run Code Online (Sandbox Code Playgroud)

但是,您可能还需要考虑使用预保存信号,具体取决于您尝试执行的操作 - 通常会添加在Django中的其余模型保存过程之前应该发生的功能.


至于为什么会发生这种情况,请考虑在评估代码时会发生什么.

首先,声明一个类.我们将引用这个原始类定义,Foo以区别于装饰器将创建的后面的类定义.这个类有save一个super(AccountForm, self).save(...)调用方法.

然后将该类传递给装饰器,装饰器定义了一个我们将调用Bar并继承自的类Foo.因此,Bar.save相当于Foo.save- 它也调用super(AccountForm, self).save(...).然后从装饰器返回第二个类.

返回的class(Bar)被分配给名称AccountForm.

因此,在创建AccountForm对象时,您将创建一个类型的对象Bar.当你调用.save(...)它时,它会查找Bar.save,这实际上是Foo.save因为它继承Foo并且从未被覆盖.

正如我们之前提到的,Foo.save电话super(AccountForm, self).save(...).问题是因为类装饰器,AccountForm不是Foo,它是Bar- 而且Bar是父级的Foo.

所以,当Foo.save查找AccountForm的母公司,它得到... Foo.这意味着当它试图调用.save(...)那个父级时,它实际上只是最终调用自身,因此无休止的递归.

  • 该问题的另一个解决方案是给原始类一个不同的名称,然后手动调用装饰器来创建您打算使用的类(例如`AccountForm`).只要`super`调用使用原始类的名称(而不是装饰版本的名称),它应该按预期工作. (2认同)