43

I have this base class and subclass:

class Event:
    def __init__(self, sr1=None, foobar=None):
        self.sr1 = sr1
        self.foobar = foobar


# Event class wrappers to provide syntatic sugar
class TypeTwoEvent(Event):
    def __init__(self, level=None):
        self.sr1 = level

Later on, when I try to check the foobar attribute of a TypeTwoEvent instance, I get an exception. For example, testing this at the REPL:

>>> event = TypeTwoEvent()
>>> event.foobar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'TypeTwoEvent' object has no attribute 'foobar'

I thought that the base class attributes would be inherited by the subclass and that creating an instance of a subclass would instantiate the base class (and thus invoke its constructor). Therefore, I expected the foobar attribute value to be defaulted to None.

Why do TypeTwoEvent instances not have a foobar attribute, even though Event instances do?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Homunculus Reticulli
  • 65,167
  • 81
  • 216
  • 341
  • 3
    As said below, you need to explicitly indicate that you want the superclasses to initialise as well. But __take care__: if you have any multiple inheritance then making this happen becomes very delicate. – Katriel Apr 22 '12 at 14:12

5 Answers5

45

The subclass should be:

class TypeTwoEvent(Event):    
    def __init__(self, level=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.sr1 = level

Because __init__ is overridden, the base class' __init__ code will only run if it is explicitly requested.

Despite its strange name, __init__ is not specially treated. It gets called automatically after the object is created; but otherwise it's an ordinary method, and ordinary inheritance rules apply.

super().__init__(arguments, that, go, to, parents)

is the syntax to call the parent version of the method. Using *args and **kwargs allows us to catch additional arguments passed to __init__ and pass them to the parent method; this way, when a TypeTwoEvent is created, a value can be specified for the foobar, along with anything else specific to the base class.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Bite code
  • 578,959
  • 113
  • 301
  • 329
4

You're overriding the constructor (__init__) of the parent class. To extend it, you need to explicitly call the constructor of the parent with a super() call.

class TypeTwoEvent(Event):
    def __init__(self, level=None, **kwargs):
        # the super call to set the attributes in the parent class
        super().__init__(**kwargs)
        # now, extend other attributes
        self.sr1 = level
        self.state = STATE_EVENT_TWO

Note that the super call is not always at the top of the __init__ method in your sub-class. Its location depends on your situation and logic.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Praveen Gollakota
  • 37,112
  • 11
  • 62
  • 61
  • "Its location depends on your situation and logic." This is true, but it would be more helpful to explain the consequences of writing it later instead of at the top - what problems can be solved (or created) this way. – Karl Knechtel May 05 '23 at 03:17
2

When the instance is created, its __init__ method is called. In this case, that is TypeTwoEvent.__init__. Superclass methods will not be called automatically because that would be immensely confusing.

You should call Event.__init__(self, ...) from TypeTwoEvent.__init__ (or use super, but if you're not familiar with it, read up on it first so you know what you're doing).

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
1

You need to call the __init__ method of the base class from the __init__ method of the inherited class.

See here for how to do this.

Community
  • 1
  • 1
alan
  • 4,752
  • 21
  • 30
0

I've had the same problem, but in my case I put super().__init__() on the bottom of my derived class and that's why it doesn't work. Because I tried to use attributes that are not initialized.

М.Б.
  • 1,308
  • 17
  • 20
  • I have the same problem did you find a solution by any chance? – enes islam Aug 08 '22 at 09:44
  • Hey @enesislam, yes, as far as I remember, it was something with trying to use attributes from parent class, but parent weren't initialized by calling parent constructor before. – М.Б. Aug 09 '22 at 10:02
  • 1
    Hey! Thank you for your answer. I solved my problem. I had `self.parent` in the parent class. When I try to use `super().__init__()` in child class it said the object has no attribute. So I have tried to use like `super().__init__(parent=self.parent)` but the result was the same. The true way to solve this problem is to send that parent value by args. So it should be: `super().__init__(*args, **kwargs)` – enes islam Aug 09 '22 at 12:55
  • @enesislam Good catch! Glad you managed to get it solved! – М.Б. Aug 09 '22 at 15:27