Python 2.3.3 super() behaviour

N

Nicolas Lehuen

Hi,

I hope this is not a FAQ, but I have trouble understanding the behaviour of
the super() built-in function. I've read the excellent book 'Python in a
Nutshell' which explains this built-in function on pages 89-90. Based on the
example on page 90, I wrote this test code :

class A(object):
def test(self):
print 'A'

class B(object):
def test(self):
print 'B'

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

print C.__mro__
c=C()
c.test()

The output is :
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type
'object'>)
A
C

Whereas I was expecting :
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type
'object'>)
A
B
C

Was I wrong to expect this (based on what I've read ?)

Regards,
Nicolas
 
P

Peter Otten

Nicolas said:
Hi,

I hope this is not a FAQ, but I have trouble understanding the behaviour
of the super() built-in function. I've read the excellent book 'Python in
a Nutshell' which explains this built-in function on pages 89-90. Based on
the example on page 90, I wrote this test code :

class A(object):
def test(self):
print 'A'

class B(object):
def test(self):
print 'B'

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

print C.__mro__
c=C()
c.test()

The output is :
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type
'object'>)
A
C

Whereas I was expecting :
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type
'object'>)
A
B
C

Was I wrong to expect this (based on what I've read ?)

As soon as a test() method without the super(...).test() is reached, no
further test methods will be invoked. Only the first in the list of base
classes will be invoked. If I'm getting it right you have to do something
like:

class Base(object):
def test(self):
print "base"

class D1(Base):
def test(self):
super(D1, self).test()
print "derived 1"

class D2(Base):
def test(self):
super(D2, self).test()
print "derived 2"

class All(D1, D2):
pass

All().test()

Here all cooperating methods have a super() call, and the base class acts as
a showstopper to prevent that Python tries to invoke the non-existent
object.test().

Peter
 
N

Nicolas Lehuen

OK, I get it now, thanks.

super() method calls should only be used for method involved in
diamond-shaped inheritance. This is logical since in this case the base
classe (from which the diamond-shaped inheritance starts) defines the
interface of the method.

This solves another question I was asking myself about super() : "how can it
work when the method signature differ between B and C ?". Answer : the
method signature should not change because polymorphic calls would be
greatly endangered. The base class defines the signature of the method which
must be followed by all its children, this way super() can work properly.

The base method signature is not enforced by Python, of course, but you'd
better respect it unless you get weird result in polymorphic calls.

Regards,
Nicolas
 
N

Nicolas Lehuen

The only problem I have is that I want to build a multiple inheritance
involving an object which does not cooperate, namely :

class T(object):
def __init__(self):
super(T,self).__init__()

class TL(list,object):
def __init__(self)
super(TL,self).__init__()

In this case, T.__init__ is not called, because list.__init__ does not use
super(). The only clean way to proceed is to change the inheritance order :
TL(T,list). This way, both constructors are called.

Here is another example which exhibits the behaviour :

class A(object):
def __init__(self):
super(A,self).__init__()
print 'A'

class B(object):
def __init__(self):
print 'B'

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

class D(A,B):
def __init__(self):
super(D,self).__init__()
print 'D'
B
A
D
<__main__.D object at 0x008F39F0>

The problem is that if you go further down the inheritance, the behaviour is
even more complicated :

class E(object):
def __init__(self):
super(E,self).__init__()
print 'E'

class F(C,E):
def __init__(self):
super(F,self).__init__()
print 'F'

class G(D,E):
def __init__(self):
super(G,self).__init__()
print 'G'
B
A
D
G
<__main__.G object at 0x008F3EF0>

class H(E,C):
def __init__(self):
super(H,self).__init__()
print 'H'

class I(E,D):
def __init__(self):
super(I,self).__init__()
print 'I'
B
A
D
E
I
<__main__.I object at 0x008F3FD0>

So the conclusion is : never do that :). Another more constructive
conclusion would be : always put the most cooperative classes first in the
inheritance declaration, provided that it doesn't interfere with your needs.
A class which has an uncooperative ancestor is less cooperative than a class
which has only cooperative ancestors.

Regards,
Nicolas
 
D

David Fraser

super is not going to be helpful here, so why not call the X.__init__
functions explicitly?
Since you *need* multiple __init__ calls from the multiply-inherited
class, super isn't going to work...
 
N

Nicolas Lehuen

Ah, so there *is* more than one way to do it, then ? ;)

What the example shows is that super(...).__init__ does make one call to
each superclass' __init__, provided that those superclasses play nicely and
use super() themselves (as Peter wrote). If one of the superclasses does not
use super(), the 'magical' iteration over parent methods is interrupted,
hence the need to put those bad guys at the end of the superclasses list in
the class declaration.

But you're right, David, the simplest way to get what I want is to
explicitely call the superclasses' __init__. However, this would mean that
super() is not as useful as it seems. I'd rather find a way to *always* use
super() than have special cases for certain inheritance trees. In other
words, I'd rather have only one way to do it :).

Regards,
Nicolas
 
J

Josef Meile

Peter said:
> As soon as a test() method without the super(...).test() is reached, no
> further test methods will be invoked. Only the first in the list of base
> classes will be invoked. If I'm getting it right you have to do something
> like:
>
> class Base(object):
> def test(self):
> print "base"
>
> class D1(Base):
> def test(self):
> super(D1, self).test()
> print "derived 1"
>
> class D2(Base):
> def test(self):
> super(D2, self).test()
> print "derived 2"
>
> class All(D1, D2):
> pass
>
> All().test()
Ok, this produces almost what the original poster wanted. You
just have to invert the order of the base classes in All:
.... pass
....

then you will get:base
derived 1
derived 2

However, I don't understand jet why this doesn't print:

base
derived 1
base
derived 2

The method test of Base is called just once? Why?
I taught it was something like:

All.test() -> D1.test() + D2.test()

which is the same as:

(Base.test() + print "derived 1") + (Base.test() + print derived 2")

Thanks in advanced,
Josef
 
P

Peter Otten

Josef said:
Ok, this produces almost what the original poster wanted. You
just have to invert the order of the base classes in All:

... pass
...

then you will get:
base
derived 1
derived 2

However, I don't understand jet why this doesn't print:

base
derived 1
base
derived 2

The method test of Base is called just once? Why?
I taught it was something like:

All.test() -> D1.test() + D2.test()

which is the same as:

(Base.test() + print "derived 1") + (Base.test() + print derived 2")

Thanks in advanced,
Josef

Every instance has just one __dict__, so calling the same Base method twice
would at best be redundant. The Python designers are a bit smarter than
that and implemented "cooperative" methods for newstyle classes. See
http://www.python.org/2.2/descrintro.html#cooperation for the details.

Peter
 
A

A. Lloyd Flanagan

Nicolas Lehuen said:
In this case, T.__init__ is not called, because list.__init__ does not use
super(). The only clean way to proceed is to change the inheritance order :
TL(T,list). This way, both constructors are called.

I'm surprised that a built-in type doesn't work with super(). Was
this a design decision, and if so why, or is this something that
should be fixed?
 
D

David Fraser

Hi Nicolas

Another related idea:
Use template functions when you have enough control over the objects:

def A(base=object):
class A(base):
def __init__(self):
print "A"
super(A, self).__init__()
return A

def B(base=object):
class B(base):
def __init__(self):
print "B"
super(B, self).__init__()
return B

class C(B(A())):
def __init__(self):
print "C"
super(C, self).__init__()

C()

This is a bit different to multiple inheritance, but can have nice
effects...

David
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top