How to Achieve Inheritance & Polymorphism in Python

The aim of this page📝 is to describe how Python’s polymorphism is achieved with the use of super() method.

Pavol Kutaj
3 min readOct 27, 2022
  • Let’s have a child/derive class — just a definition
class RefrigeratedShippingContainer(ShippingContainer):
< DERIVED_CLASS >< BASE_CLASS >
  • The statement means that class methods defined in the base get inherited in the derived
  • The derived class is also known as the subclass/child class

Repeat that class attributes are defined without prefixes and called with <class_name> prefix

# DEFINE
class RefrigeratedShippingContainer(ShippingContainer):

MAX_CELSIUS = 4.0
# CALL
RefrigeratedShippingContainer.MAX_CELSIUS

The overriding of __init()__ in the derived class must be explicitly done with super().__init()__

  • let’s assume that we need to override the initializer of the base class because we need a new instance attribute
  • in this example, it is the temperature of the container (celsius) — it occurs only in derived classes
  • of course, I want to make some validation here and raise an error if the temperature is too high
  • the base class has nothing to do with this and should know nothing about that → polymorphism
  • we are still calling the __init__() within the base class as it sets some instance attributes we need, such as owner and the actual contents of a container, attributes that are shared among all containers
  • allegedly, in other languages, it is the case that constructors at every level in an inheritance hierarchy will be called automatically

…but not in python

  • in other words, the following will not call the base class __init__() ...no matter that you are defining inheritance relationship in the
RefrigeratedShippingContainer(ShippingContainer)
^^^^^^^^^^^^^^^^^signature of the class definition
  • the following is incomplete and not calling __init__() from the parent (ShippingContainer)
class RefrigeratedShippingContainer(ShippingContainer):

MAX_CELSIUS = 4.0

def __init__(self, celsius):
if celsius > RefrigeratedShippingContainer.MAX_CELSIUS:
raise ValueError("Temperature too hot!")
self.celsius = celsius

""" TYPE ERROR - YOU ARE NOT BEING EXPLICIT ABOUT YOUR PARENTS! """
>>> example = RefrigeratedShippingContainer(owner_code="MRP", contents="poetry", celsius=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'owner_code'
  • in other words, once you start defining __init__() in a derived class, you need to be explicit
  • explicit about all of the instance attributes
  • Remember the zen of python: Explicit is better than implicit and thus:
  1. call the built-in super() that gets the reference to the base class passed as an argument in the class definition
  2. call __init__() on the returned reference and forward the constructor args
  3. perform the override in what follows
class RefrigeratedShippingContainer(ShippingContainer):

MAX_CELSIUS = 4.0

def __init__(self, owner_code, contents, celsius):
super().__init__(owner_code, contents) # * calling the base class version of __init__
""" 🠋 OVERRIDE 🠋 """
if celsius > RefrigeratedShippingContainer.MAX_CELSIUS:
raise ValueError("Temperature too hot!")
self.celsius = celsius
  • in this case, we are inheriting the constructor from the base class with its setting of owner code + contents + with the generation of a bic_code which has the shared logic for any container anywhere

Not calling super() in the derived initializer will fire AttributeError

  • if you have
class RefrigeratedShippingContainer(ShippingContainer):

MAX_CELSIUS = 4.0

def __init__(self, owner_code, contents, celsius):
# super().__init__(owner_code, contents)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ commented out
if celsius > RefrigeratedShippingContainer.MAX_CELSIUS:
raise ValueError("Temperature too hot!")
self.celsius = celsius

… the instance initialization will not fail on you

  • you will get an error only when you attempt to call that property that you may expect has been set (inherited!) — but it was not!
  • it is not enough if the class definition refers to the inherited with
>>> f = RefrigeratedShippingContainer(owner_code="MRP",contents=["books"], celsius=-1)

# WORKS --> SET IN THE CHILD CLASS

>>> f.celsius
-1

# FAILS --> PYTHON INHERITS EXPLICITLY!
>>> f.owner_code
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'RefrigeratedShippingContainer' object has no attribute 'owner_code'

'RefrigeratedShippingContainer' object has no attribute 'owner_code'

>>> f.contents
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'RefrigeratedShippingContainer' object has no attribute 'contents'

'RefrigeratedShippingContainer' object has no attribute 'contents'

--

--

No responses yet