Jumping over in the class hierarchy

Jan Niklas Fingerle usenet-2004 at lithe.de
Tue Aug 1 16:39:15 EDT 2006


Pupeno <pupeno at pupeno.com> wrote:
> be correct to do: super(A, super(B, self)).method() in C ?

Why do you want to use super? Is there any diamond shaped inheritance
in sight? Anyway, have you actually tried, what you suggested? Well, ...

----------------------8<------------------------------------
class A(object):
    def my_method(self):
        print 'in A'

class B(A):
    def my_method(self):
        print 'in B'
        super(B, self).my_method()

class C(B):
    def my_method(self):
        print 'in C'
        super(A, super(B, self)).my_method()

print "A..."
A().my_method()
print "B..."
B().my_method()
print "C..."
C().my_method()
---------------------->8------------------------------------

leads to

----------------------8<------------------------------------
A...
in A
B...
in B
in A
C...
in C
Traceback (most recent call last):
  File "test.py", line 20, in ?
    C().my_method()
  File "test.py", line 13, in my_method
    super(A, super(B, self)).my_method()
TypeError: super(type, obj): obj must be an instance or subtype of type
---------------------->8------------------------------------

, at least on my machine ;-)

"obj must be an instance or subtype of type". So, what's the type
of "super(B, self)"? Let's see...

----------------------8<------------------------------------
class A(object):
    def my_method(self):
        print 'in A'

class B(A):
    def my_method(self):
        print 'in B'
        super(B, self).my_method()

class C(B):
    def my_method(self):
        print 'in C'
        print type(super(B, self))

C().my_method()
---------------------->8------------------------------------

----------------------8<------------------------------------
in C
<type 'super'>
---------------------->8------------------------------------

Seems, that super is a class. In fact "help(super)" at the
interactive prompt tells you that "super(type, obj) -> bound super
object; requires isinstance(obj, type)". Now, the interactive help 
isn't much more help, if you don't know what super does. This isn't
the interactive help's (aka super's doc string's) fault. It's just
that the docstring would become veeeery long if it wanted to 
introduce the concepts behind super.

Well, now, what does super do? Let's take a simple example. Well,
at least the simplest example super was really meant to handle:
(real) diamond shape inheritance.

----------------------8<------------------------------------
class A(object):
    def my_method(self):
        print 'in A'

class B(A):
    def my_method(self):
        print 'in B'
        super(B, self).my_method()

class C(A):
    def my_method(self):
        print 'in C'
        super(C, self).my_method()

class D(B,C):
    def my_method(self):
        print 'in D'
        super(D, self).my_method()

obj = D()
obj.my_method()
---------------------->8------------------------------------

----------------------8<------------------------------------
in D
in B
in C
in A
---------------------->8------------------------------------

What happens here?

(1) We instantiate a D object named "obj" and call its my_method.

(2) We instantiate a super object. We tell it, that we are
    "in" 'obj' and that we are interested in the next base class
    after handling "D". Super "asks" obj for it's ancestry (which
    is D-B-C-A). The next ancestor in this list is B. Therefore
    super(D, self).my_method() delegates the call to B.my_method.

(3) We're in B.my_method now. 

    We instantiate a super object. We tell it, that we are
    "in" 'obj' and that we are interested in the next base class
    after handling "B". Super "asks" obj for it's ancestry (which
    is D-B-C-A). The next ancestor in this list is C. Therefore
    super(B, self).my_method() delegates the call to C.my_method.
 
(4) We're in C.my_method now.

    We instantiate a super object. We tell it, that we are
    "in" 'obj' and that we are interested in the next base class
    after handling "C". Super "asks" obj for it's ancestry (which
    is D-B-C-A). The next ancestor in this list is A. Therefore
    super(B, self).my_method() delegates the call to A.my_method.

(5) We're in A.my_method now. Nothing more of interest will happen...

Note:

(a) C.my_method "gets called by" B.my_method, even though these
    two classes don't "know" anything about each other.
(b) C.my_method would never have been called, if D used super,
    but B didn't (but called its parent directly). 
(c) Even though B and C don't know anything about each other, 
    they have to care for each other's argument lists. Well, yes,
    this seems to be no problem, since a child classes method 
    should have "the same" argument list as the base class anyway.
    Yet, this doesn't hold for __init__, so you have to handle 
    __init__ with special care (i.e. you should only use **kwargs and
    propagate *all* parameters).
(d) "super()" *does not* "cast" a child class object to the
    parent class (or something like this). "super(B, self)"
    *does not* return self as "B's super class" object. This
    wouldn't work correctly for diamond shapes anyway.

Now that we've understood, what super() does, you could use

----------------------8<------------------------------------
class A(object):
    def my_method(self):
        print 'in A'

class B(A):
    def my_method(self):
        print 'in B'
        super(B, self).my_method()

class C(B):
    def my_method(self):
        print 'in C'
        super(B, self).my_method()

print "A..."
A().my_method()
print "B..."
B().my_method()
print "C..."
C().my_method()
---------------------->8------------------------------------

----------------------8<------------------------------------
A...
in A
B...
in B
in A
C...
in C
in A
---------------------->8------------------------------------

, but: Is it really the super magic, that you need? As long, as
you don't have diamond shape inheritance, you can always call the 
base classes method directly. And casually using super doesn't help,
either, because (see note (b)) all classes in the diamond shape *must* 
use super for the magic to work. In my world, this is only worth it,
whenever I really need this magic.

So, to cut a long story short, I would just use:

----------------------8<------------------------------------
class A(object):
    def my_method(self):
        print 'in A'

class B(A):
    def my_method(self):
        print 'in B'
        A.my_method(self)

class C(A):
    def my_method(self):
        print 'in C'
        A.my_method(self)

print "A..."
A().my_method()
print "B..."
B().my_method()
print "C..."
C().my_method()
---------------------->8------------------------------------

----------------------8<------------------------------------
A...
in A
B...
in B
in A
C...
in C
in A
---------------------->8------------------------------------

I hope, this helped. FWIW, i think super is a cool tool. Like a chain
saw. Use it where appropriate, then it will be real help. I also don't
consider super() "harmful" (Google will tell you, what I'm referring
to.) I just consider it to be named misleadingly, since it does *not*
return the super class or an object re-typed to the super class. But,
then again, Python is not Java... ;-) 

Cheers,
  --Jan Niklas



More information about the Python-list mailing list