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.
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
- Remember Constants are uppercase < PEP 8: The Style Guide for Python Code
- Also, see How to Implement Class Attributes in Python Classes
- In short, define with no prefix → call within the class with the class prefix → for example:
# 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 asowner
and the actualcontents
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:
- call the built-in
super()
that gets the reference to the base class passed as an argument in the class definition - call
__init__()
on the returned reference and forward the constructor args - 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'