6

I have an abstract base class Bicycle:

from abc import ABC, abstractmethod

class Bicycle(ABC):

    def __init__(self, cadence = 10, gear = 10, speed = 10):
        self._cadence = cadence
        self._gear = gear         
        self._speed = speed

    @abstractmethod
    def ride(self):
        pass

    def __str__(self):
        return  "Cadence: {0}  Gear: {1}  Speed: {2}".format(self._cadence, 
                                                             self._gear, self._speed)

and a subclass MountainBike:

from Bicycle import Bicycle

class MountainBike(Bicycle):

    def __init__(self):
        super().__init__(self)


    def ride(self):
        return "Riding my Bike"

The following code will cause a recursion error, but if I remove self from the super().__init__(self), the call to __str__(self): works.

Question:

  1. I only discovered this error when I implemented the __str__(self):

    In Python 3.x when calling the parent constructor from the child with no arguments, is passing self, necessary?

  2. Suppose MountainBike now sets the cadence, gear, speed this means in my subclass the constructor will look like this:

    class MountainBike(Bicycle):
    
        def __init__(self, cadence, gear, speed):
            super().__init__(cadence,gear,speed)
    

notice, self isn't being passed in the super because to my knowledge, it can throw the variable assignments off. Is this assumption correct?

  • In python 3 the `self` is not needed as I believe that `super` does that for you, so: `super().__init__()` is sufficient (plus `cadence,gear,speed` as needed in your case). – quamrana May 20 '18 at 17:28
  • So, either way, with or without arguments, passing `self` isn't necessary, correct? –  May 20 '18 at 17:30
  • That's correct. – quamrana May 20 '18 at 17:32
  • According to the official documentation, it is listed as mandatory. https://docs.python.org/3/reference/datamodel.html#object.__init__ – Attersson May 20 '18 at 17:34
  • @Attersson - It doesn't say you should pass `self`, it just says that the `super().__init__()` should be called from a subclass which is necessary. –  May 20 '18 at 17:42
  • 1
    Right. It is mandatory in the first `__init__` but should be omitted in the `super().__init__()` because it is already implicitly passed there. – Attersson May 20 '18 at 17:44
  • @Attersson - Right! –  May 20 '18 at 17:44
  • Actually it is not even mandatory in the first `__init__` . If you omit it, all the variables used in the init would be local and only exist until the function terminates. It may be appropriate if you don't want to touch the instance. – Attersson May 20 '18 at 17:45

1 Answers1

3

self is passed implicitly to the super call, so adding it explicitly sends it twice:

def __init__(self):
    super().__init__(self)

That ends up calling Bicycle(self, self), which is the same as Bicycle(self, cadence=self).

Later on, you have probably tried convert your instance to str (e.g. to print it), so this was called:

def __str__(self):
    return  "Cadence: {0}  Gear: {1}  Speed: {2}".format(self._cadence, 
                                                         self._gear, self._speed)

That code tried to convert self._cadence to a string and self._cadence is self because of the previous error, so it continues in an endless recursion (until the recursion exception).


Note that super() takes two forms: with arguments and without arguments, so there are two correct ways to fix the code.

The Python 3 way (without arguments):

def __init__(self):
    super().__init__()

The old Python 2 way, which is more explicit:

def __init__(self):
    super(MountainBike, self).__init__()

Both do the same, i.e. they give you the bound __init__ method which already has the implicit self.

See also here: https://docs.python.org/3/library/functions.html#super

zvone
  • 18,045
  • 3
  • 49
  • 77