3

I've got an inheritance chain of three classes: Baz inherits from Bar inherits from Foo. I'd like to define a method in the Foo class that, when called in a subclass, returns the output of its parent class appended with stuff of its own. This is the desired output:

>>> foo = Foo()
>>> bar = Bar()
>>> baz = Baz()
>>> print foo.get_defining_fields()
['in Foo']
>>> print bar.get_defining_fields()
['in Foo', 'in Bar']
>>> print baz.get_defining_fields()
['in Foo', 'in Bar', 'in Baz']

The problem is, I'm misunderstanding something about the use of super() in superclass methods that are called by subclasses, or some other subclassing detail. This bit works fine:

>>> print foo.get_defining_fields()
['in Foo']

But bar.get_defining_fields() produces an infinite loop, running itself until a RuntimeError is raised instead of calling out to foo.get_defining_fields and stopping there like I want it to.

Here's the code.

class Foo(object):
    def _defining_fields(self):
        return ['in Foo']

    def get_defining_fields(self):
        if self.__class__ == Foo:
            # Top of the chain, don't call super()
            return self._defining_fields()
        return super(self.__class__, self).get_defining_fields() + self._defining_fields()

class Bar(Foo):
    def _defining_fields(self):
        return ['in Bar']

class Baz(Bar):
    def _defining_fields(self):
        return ['in Baz']

So get_defining_fields is defined in the superclass, and the super() call within it uses self.__class__ in an attempt to pass the correct subclass name to the call in each subclass. When called in Bar, it resolves to super(Bar, self).get_defining_fields() so that the list returned by foo.get_defining_fields() would be prepended to the one returned by far.get_defining_fields().

Probably a simple mistake if you properly understand Python's inheritance mechanics and the inner workings of super(), but since I obviously don't, I'd appreciate it if someone could point me to the proper way of doing this.


EDIT: as per Daniel Roseman's answer, I tried replacing the super() call with this form: return super(Foo, self).get_defining_fields() + self._defining_fields()

Now the infinite recursion no longer occurs, but I get a different error when calling bar.get_defining_fields():

AttributeError: 'super' object has no attribute 'get_defining_fields'

Something else is still amiss.


EDIT: yep, finally figured out what I was missing here. Marked Daniel's updated answer as the accepted one.

2
  • 1
    @Charles Beattie: This is true for Python 3 where all classes are new-style classes, but not encouraged in Python 2. Commented Jul 3, 2011 at 11:16
  • I deleted my comment but I was asking before why he was deriving from object. Hence jena's reply. Commented Jul 3, 2011 at 14:46

1 Answer 1

8

The problem is here:

 return super(self.__class__, self)...

self.__class__ always refers to the current concrete class. So in Bar, it refers to Bar, and in Baz, it refers to Baz. So, Bar calls the superclass's method, but the self.__class__ still refers to Bar, not Foo. So you will get an endless recursion.

That's why you must always refer to the class specifically in super. Like this:

return super(Foo, self)...

Edit Right, because only the top class defines get_defining_fields.

You're going about this the wrong way, really. This isn't a job for super at all. I think you might get better results iterating through self.__class__.__mro__, which is the way to access the method resolution order of the superclasses:

class Foo(object):
    def _defining_fields(self):
        return ['in Foo']

    def get_defining_fields(self):
        fields = []
        for cls in self.__class__.__mro__:
            if hasattr(cls, '_defining_fields'):
                fields.append(cls._defining_fields(self))
        return fields

¬

Sign up to request clarification or add additional context in comments.

2 Comments

That solved the recursion, but brought about an AttributeError instead. I edited the question accordingly.
Yeah, my thinking was muddled, I was under the impression that Bar would inherit get_defining_fields as-is and running it from under Bar would make it "Bar's method" as far as the super() call was concerned.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.