Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python class decorator extending class causes recursion

I'm overwriting the save method of a ModelForm and I don't know why it would cause recursion:

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

Causes this:

maximum recursion depth exceeded while calling a Python object

Stacktrace shows this line repetitively calling itself:

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

Now, the parsley decorator is like this:

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

As @DanielRoseman suggested that the Parsley decorator extending the AccountForm causes the super(AccountForm,self) to keep calling itself, what's the solution?

Also I cannot get my head around this why this would cause recursion.

like image 922
James Lin Avatar asked Feb 06 '13 22:02

James Lin


1 Answers

What you could do is just call the parent's method directly:

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

This should neatly avoid the issue introduced by your class decorator. Another option would be to manually call the decorator on a differently named base class, rather than using @ syntax:

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

AccountForm = parsleyfy(AccountFormBase)

However, you might also want to consider using a pre-save signal instead, depending on what you're trying to do - it's how one normally adds functionality that should happen before the rest of the model save process in Django.


As for why this is occurring, consider what happens when the code is evaluated.

First, a class is declared. We'll refer to this original class definition as Foo to distinguish it from the later class definition that the decorator will create. This class has a save method which makes a super(AccountForm, self).save(...) call.

This class is then passed to the decorator, which defines a new class which we'll call Bar, and inherits from Foo. Thus, Bar.save is equivalent to Foo.save - it also calls super(AccountForm, self).save(...). This second class is then returned from the decorator.

The returned class (Bar) is assigned to the name AccountForm.

So when you create an AccountForm object, you're creating an object of type Bar. When you call .save(...) on it, it goes and looks up Bar.save, which is actually Foo.save because it inherited from Foo and was never overridden.

As we noted before, Foo.save calls super(AccountForm, self).save(...). The problem is that because of the class decorator, AccountForm isn't Foo, it's Bar - and Bar's parent is Foo.

So when Foo.save looks up AccountForm's parent, it gets... Foo. This means that when it tries to call .save(...) on that parent, it actually just winds up calling itself, hence the endless recursion.

like image 115
Amber Avatar answered Nov 03 '22 15:11

Amber