[Tutor] Inheritance

Gonçalo Rodrigues op73418 at mail.telepac.pt
Wed Dec 17 07:11:28 EST 2003


On Tue, 16 Dec 2003 13:34:17 -0700, you wrote:

>I'm just starting to learn classes and have run into trouble understanding 
>how classes inherit. 
>
>One question is if class A inherits from class B and C. And then B inherits 
>from M and N. And C inherits from F and G, while F and N inherit from class 
>z which inherits from object, what is the name resolution. 
>
> 
>
>                       Z
>                 M  N    F  G
>                  B       C
>                      A 
>
> 
>
>Also can someone explain to me what super() does, i need something pretty 
>basic. 
>

D. Ehrenberg already answered some of your questions so I'll try to
explain the why's and the how's of super. super is explained by the
BDFL himself in an essay (all advanced stuff)

http://www.python.org/2.2/descrintro.html


(Warning: long and I dare say, advanced, post ahead)

For starters imagine we have the following inheritance graph

 object
   A
B   C
   D

Let me be more specific and write the class definitions

>>> class A(object):
... 	def __init__(self):
... 		print "A"
... 
>>> class B(A):
... 	def __init__(self):
... 		print "B"
... 
>>> class C(A):
... 	def __init__(self):
... 		print "C"
... 
>>> class D(B,C):
... 	def __init__(self):
... 		print "D"
... 
>>> d = D()
D

The class D inherits from B and C (and from A indirectly) but the
initializer __init__ never calls it's super classe's __init__. But
this is almost always never what you want! Since the super classe's
__init__ can contain fundamental initialization code for their
instances to work. So the problem is:

How to call from D.__init__ all the super classe's __init__?

As you know, when Python, finds something like

d.<an attribute>

it uses (roughly - there are details I'm hiding) the following name
look up rule:

1. Look in the instance's dictionary d.__dict__

2. If 1. fails look in the instance's class dictionary
d.__class__.__dict__

3. If 2. fails crawl through the super classe's dictionary looking in
their dictionaries.

4. If 3. fails call __getattr__

I'm interested in point 3. This crawling "through the super classe's"
is achieved by linearizing the inheritance graph, that is, we
transform the graph into a list. You can see that by calling mro on
the *class* object:

>>> D.mro()
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>,
<class '__main__.A'>, <type 'object'>]

So the search order is D, B, C, A, object.

We can fold this knowledge in coding our __init__.

>>> class D(B,C):
... 	def __init__(self):
... 		B.__init__(self)
... 		print "D"
... 		
>>> d = D()
B
D

Hmm... still not enough. We have to change B and C's __init__ to call
their super classe's __init__.

>>> class B(A):
... 	def __init__(self):
... 		A.__init__(self)
... 		print "B"
... 		
>>> b = B()
A
B

>>> class C(A):
... 	def __init__(self):
... 		A.__init__(self)
... 		print "C"
... 		
>>> c = C()
A
C

We don't do it with the A class, because object.__init__ is a no-op -
does nothing.

But for the classe's B and C things seem to be working - The super
classe's __init__ is being called. Let us now test D, the base of our
diamond inheritance graph:

>>> d = D()
A
B
D

Hmmm... C.__init__ is not being called. Can you see why it is so?
(D.__init__ calls B.__init__ calls A.__init__)

Let us recode it and call it explicitely:

>>> class D(B,C):
... 	def __init__(self):
... 		B.__init__(self)
... 		C.__init__(self)
... 		print "D"
... 		
>>> d = D()
A
B
A
C
D

Now A.__init__ is being called twice! Can you see why it is so?
(D.__init__ calls B.__init__ calls A.__init__. Then it calls
C.__init__ calls A.__init__)

Not good, since what we want is that each super classe's __init__ to
be called exactly *once*. Notice that if you had single inheritance
the above scheme would work just fine and dandy. It is multiple
inheritance (D inherits from B and C) that makes things tougher. And
this is precisely the problem that super is designed to solve.

>>> class B(A):
... 	def __init__(self):
... 		super(B, self).__init__()
... 		print "B"
... 
>>> b = B()
A
B
>>> class C(A):
... 	def __init__(self):
... 		super(C, self).__init__()
... 		print "C"
... 
>>> c = C()
A
C
>>> class D(B,C):
... 	def __init__(self):
... 		super(D, self).__init__()
... 		print "D"
... 
>>> d = D()
A
C
B
D
>>> 

Now it all works as you see. The structure of the super call is very
simple:

super(<the class>, <the instance>).<the method>(<arguments - but no
self!>)

The online help is more explicit:

>>> help(super)
Help on class super in module __builtin__:

class super(object)
 |  super(type) -> unbound super object
 |  super(type, obj) -> bound super object; requires isinstance(obj,
type)
 |  super(type, type2) -> bound super object; requires
issubclass(type2, type)
 |  Typical use to call a cooperative superclass method:
 |  class C(B):
 |      def meth(self, arg):
 |          super(C, self).meth(arg)
 |  
....

Hope it helps, with my best regards,
G. Rodrigues



More information about the Tutor mailing list