【问题标题】:Python class decorator extending class causes recursionPython类装饰器扩展类导致递归
【发布时间】:2024-01-14 03:49:01
【问题描述】:

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

@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

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

return super(AccountForm, self).save(*args,**kwargs) 

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

def parsleyfy(klass):
    class ParsleyClass(klass):
      # some code here to add more stuff to the class
    return ParsleyClass

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

我也无法理解为什么这会导致递归。

【问题讨论】:

  • 我不认为你应该返回父 save() 方法,只做 super(AccountForm, self).save(*args,**kwargs)
  • 您确定这是您的实际代码吗?如果您在 super 调用中错误地引用了超类,通常会发生这种情况 - 例如,您实际上有一个 AccountForm 的子类,并且在该覆盖的保存方法中您仍在调用 super(AccountForm...)
  • 谢谢@DanielRoseman 我已经更新了问题
  • @PepperoniPizza 你应该肯定返回一个modelform超级保存,因为它应该返回一个实例
  • @JamesLin ParsleyClass 做什么?你能提供一个更完整的例子吗?

标签: python django forms recursion save


【解决方案1】:

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

@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)

但是,您可能还想考虑改用 pre-save signal,具体取决于您要执行的操作 - 通常这是在 Django 中的模型保存过程的其余部分之前添加功能的方式。


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

首先,声明一个类。我们将这个原始类定义称为Foo,以将其与装饰器将创建的后续类定义区分开来。这个类有一个save 方法,它可以进行super(AccountForm, self).save(...) 调用。

然后这个类被传递给装饰器,它定义了一个我们称之为Bar的新类,并继承自Foo。因此,Bar.save 等价于Foo.save - 它也调用super(AccountForm, self).save(...)。然后从装饰器返回第二个类。

返回的类 (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(...) 时,它实际上只是调用了自身,因此是无休止的递归。

【讨论】:

  • @JamesLink 我还添加了一个关于pre-save signals 的注释,这是 Django 添加在模型保存之前立即触发的功能的首选方式。
  • 这个问题的另一个解决方案是给原始类一个不同的名字,然后手动调用装饰器来创建你打算使用的类(例如AccountForm)。只要 super 调用使用原始类的名称(而不是修饰版本的名称),它就应该按预期工作。
  • @JamesLin 在某些情况下,直接调用父级的方法可能会产生其他问题——尤其是那些涉及在同一个类上调用 other 方法的问题被覆盖。在单继承的情况下,这些问题往往不会出现,但多继承会变得非常棘手。
  • @JamesLin 我用 Blckknght 的建议示例扩展了我的答案。
  • @JamesLin super() 的复杂性太复杂了,无法真正放入单个评论线程中;如果您真的想了解更多信息,我建议您阅读artima.com/weblogs/viewpost.jsp?thread=236275docs.python.org/2/library/functions.html#super。简短的故事是,当您使用多重继承(多个超类,例如class C(A, B):)时,“父级”的概念变得更加复杂。
【解决方案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) vs super(Parent, self) 我问过这个question

【讨论】: