Pyderman
Pyderman

Reputation: 16199

When a subclass calls super() with __init__(), where/what is the superclass's object?

According to Learn Python The Hard Way ex44 and the explantion of using super() with __init__:

class Child(object): 
    def __init__(self, stuff):
        self.stuff = stuff
        super(Child, self).__init__()

This is pretty much the same as the Child.altered example above, except I'm setting some variables in the __init__ before having the Parent initialize with its Parent.__init__.

So instantiating Child causes also an instantiation of Parent. But what/where is this instantiation? Given:

c = Child()

where/what is the Parent object?

Upvotes: 1

Views: 96

Answers (2)

timgeb
timgeb

Reputation: 78700

super(Child, self) gives you the object which self is pointing to as an instance of the parent class of Child (which is Parent).

In the __init__ method of Child you first do some stuff (self.stuff = stuff) and then call the __init__ method of Parent on your Child object which self is pointing to!

You are not instantiating a Parent object explicitly, but because of inheritance every Child is a Parent.

@timgeb a practical usage example, or justification for using super() in this way, would be helpful.

Sure, let's look at a simple example. Let's say you are building an application for a postal service. The service delivers several kinds of items such as packages, letters or postcards. Each of these items share some properties. To keep it simple, let's say they share the name of the receiver (in a real world application, you would probably need more properties such as the full address of the receiver).

>>> class MailItem(object):
...     def __init__(self, receiver_name):
...         self.receiver_name = receiver_name
... 
>>> m = MailItem('Pyderman')
>>> m.receiver_name
'Pyderman'

This seems to work fine. However, our MailItem is far too unspecific. What if the item is a package? Then we would certainly care about its weight. On the other hand, if the item is a postcard, the weight would be irrelevant. Let's create a child class for the packages called Package which inherits from MailItem. Every Package object is-a MailItem, but it will store extra information about its weight.

>>> class Package(MailItem):
...     def __init__(self, receiver_name, weight):
...         # treat the package as a MailItem and call the MailItem initializier
...         super(Package, self).__init__(receiver_name)
...         # store extra information about the weight in some arbitrary unit
...         self.weight = weight

When we instantiate a Package, the first thing we do is to call the initializer of MailItem, because every Package is also a MailItem. This will not create another MailItem object, but call the __init__ method of MailItem on our new Package object. In this example, we could have achieved the same thing by copying the line self.receiver_name = receiver_name from the initializer of MailItem into the initializer of Package instead of using super, but that would be duplicate code and go against the Dont-Repeat-Yourself principle.

Let's instantiate a Package to see the code at work.

>>> p = Package('Pyderman', 200)
>>> p.receiver_name
'Pyderman'
>>> p.weight
200

As you can see, the object p does have a receiver_name, because the initializer of Package calls the MailItem initializer via
super(Package, self).__init__(receiver_name).

At the risk of repeating myself, to make one thing crystal clear: We are only creating one Package object with p = Package('Pyderman', 200), not an additional MailItem object. Every Package object is-a MailItem, and Python will tell you so:

>>> isinstance(p, MailItem)
True

Of course, every Package is also a Package.

>>> isinstance(p, Package)
True

However, not every MailItem is-a Package:

>>> m = MailItem('Pyderman')
>>> isinstance(m, Package)
False

Think about it this way: every crocodile is a reptile, but not every reptile is a crocodile.

Upvotes: 3

user4815162342
user4815162342

Reputation: 155036

Not instantiation, initialization - carefully read the passage you quoted and you'll see it only mentions the latter. Initialization (sometimes historically also referred to as construction) is the process of building up an empty instance into a usable object of the given type.

Parent.__init__ results in a usable Parent object; Child.__init__ builds upon that to create a usable Child object. Thus, every Child object is by extension also a valid Parent object.

Upvotes: 2

Related Questions