3.8. OOP Inheritance Patterns

  • no inheritance

  • single inheritance

  • multilevel inheritance

  • multiple inheritance (mixin classes)

single inheritance

One class inherits from one other class. Has one parent.

multilevel inheritance

One class inherits from other class, and yet another class inherits from it. This creates hierarchical structure.

multiple inheritance
mixin classes

One class derives from several other classes at once.

3.8.1. No Inheritance

>>> class Vehicle:
...     pass
>>>
>>>
>>> class Car:
...     pass

3.8.2. Single Inheritance

>>> class Vehicle:
...     pass
>>>
>>>
>>> class Car(Vehicle):
...     pass

3.8.3. Multilevel Inheritance

>>> class Vehicle:
...     pass
>>>
>>>
>>> class VehicleWithWindows(Vehicle):
...     pass
>>>
>>>
>>> class Car(VehicleWithWindows):
...     pass

3.8.4. Multiple Inheritance

  • HasEngine and HasWindows are Mixin Classes

  • Such classes are usually called: EngineMixin, WindowsMixin

>>> class Vehicle:
...     pass
>>>
>>> class HasEngine:
...     pass
>>>
>>> class HasWindows:
...     pass
>>>
>>>
>>> class Car(Vehicle, HasEngine, HasWindows):
...     pass
...

3.8.5. Composition

>>> class Vehicle:
...     pass
>>>
>>> class Engine:
...     pass
...
>>> class Windows:
...     pass
...
>>>
>>> class Car(Vehicle):
...     engine = Engine
...     windows = Windows

3.8.6. Aggregation

>>> class Vehicle:
...     pass
...
>>> class Engine:
...     pass
...
>>> class Windows:
...     pass
...
>>>
>>> class Car(Vehicle):
...     parts = [Engine, Windows]

3.8.7. Why Composition?

>>> class Mother:
...     pass
>>>
>>> class Father:
...     pass
>>>
>>>
>>> class Child:
...     mother: Mother
...     father: Father
...
...     def __init__(self, mother=Mother(), father=Father()):
...         self.mother = mother
...         self.father = father
>>> class StepFather:
...     pass
>>>
>>> me = Child(father=StepFather())

3.8.8. Use Case - 0x01

Following example is simple and easy to understand, but not totally accurate. Inheritance means, that a class is a specialized form of its base. This results in a subclass being an instance of a superclass. Which is weird when we think, that a Child might be its Parent in the same time.

No Inheritance:

>>> class Parent:
...     pass
>>>
>>>
>>> class Child:
...     pass

Single Inheritance:

>>> class Parent:
...     pass
>>>
>>>
>>> class Child(Parent):
...     pass

Multilevel Inheritance:

>>> class Grandparent:
...     pass
>>>
>>> class Parent(Grandparent):
...     pass
>>>
>>>
>>> class Child(Parent):
...     pass

Multiple Inheritance:

>>> class Mother:
...     pass
>>>
>>> class Father:
...     pass
>>>
>>>
>>> class Child(Mother, Father):
...     pass

Composition:

>>> class Mother:
...     pass
>>>
>>> class Father:
...     pass
>>>
>>> class Child:
...     mother = Mother
...     father = Father

Aggregation:

>>> class Mother:
...     pass
>>>
>>> class Father:
...     pass
>>>
>>> class Child:
...     parents = [Father, Mother]

3.8.9. Use Case - 0x02

>>> class Mother:
...     pass
>>>
>>> class Father:
...     pass
>>>
>>>
>>> class Child:
...     mother: Mother
...     father: Father
...
...     def __init__(self, mother=Mother(), father=Father()):
...         self.mother = mother
...         self.father = father

3.8.10. Use Case - 0x03

>>> class Vehicle:
...     engine: Engine
...     windows: Windows | None
>>>
>>> class Engine:
...     def engine_start(self): ...
...     def engine_stop(self): ...
...
>>> class Windows:
...     def window_open(self): ...
...     def window_close(self): ...
...
>>>
>>> class Car(Vehicle):
...     engine: Engine
...     windows: Windows
...
...     def __init__(self, windows=Windows(), engine=Engine()):
...         self.windows = windows
...         self.engine = engine
...
...     def engine_start(self):
...         if self.engine:
...             return self.engine.engine_start()
...
...     def engine_stop(self):
...         if self.engine:
...             return self.engine.engine_stop()
...
...     def window_open(self):
...         if self.windows:
...             return self.windows.windows_open()
...
...     def window_close(self):
...         if self.windows:
...             return self.windows.windows_close()

3.8.11. Use Case - 0x04

>>> class Encoder:
...     def encode(self, data):
...         ...
>>>
>>> class Decoder:
...     def decode(self, data):
...         ...
>>>
>>>
>>> class JSONSerializer:
...     encoder: Encoder
...     decoder: Decoder
...
...     def __init__(self,
...                  encoder: Encoder = Encoder(),
...                  decoder: Decoder = Decoder(),
...                  ) -> None:
...         self.encoder = encoder
...         self.decoder = decoder
...
...     def encode(self, data):
...        return self.encoder.encode(data)
...
...     def decode(self, data):
...         return self.decoder.decode(data)
>>>
>>>
>>> DATA = {'firstname': 'Mark', 'lastname': 'Watney'}

Now, if you want to serialize your data, just create an instance and call method .encode() on it.

>>> json = JSONSerializer()
>>> result = json.encode(DATA)

If you want to use your better version of encoder (for example which can encode datetime object. You can create a class which inherits from default Encoder and overwrite .encode() method.

>>> class MyBetterEncoder(Encoder):
...     def encode(self):
...         ...
>>>
>>> json = JSONSerializer(encoder=MyBetterEncoder)
>>> result = json.encode(DATA)

3.8.12. Use Case - 0x05

>>> from datetime import date
>>> import json
>>> DATA = {'firstname': 'Mark', 'lastname': 'Watney'}
>>>
>>> json.dumps(DATA)
'{"firstname": "Mark", "lastname": "Watney"}'
>>> DATA = {'firstname': 'Mark', 'lastname': 'Watney', 'birthdate': date(1969, 7, 21)}
>>>
>>> json.dumps(DATA)
Traceback (most recent call last):
TypeError: Object of type date is not JSON serializable
>>> class Encoder(json.JSONEncoder):
...     def default(self, x):
...         if isinstance(x, date):
...             return x.isoformat()
...
>>>
>>> DATA = {'firstname': 'Mark', 'lastname': 'Watney', 'birthdate': date(1969, 7, 21)}
>>>
>>> json.dumps(DATA, cls=Encoder)
'{"firstname": "Mark", "lastname": "Watney", "birthdate": "1969-07-21"}'

3.8.13. Further Reading

3.8.14. Assignments

Code 3.64. Solution
"""
* Assignment: OOP InheritancePatterns Simple
* Type: class assignment
* Complexity: easy
* Lines of code: 4 lines
* Time: 3 min

English:
    1. Create class `Account`
    2. Create class `User` which inherits from `Account`
    3. Run doctests - all must succeed

Polish:
    1. Stwórz klasę `Account`
    2. Stwórz klasę `User`, która dziedziczy po `Account`
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass

    >>> assert isclass(Account)
    >>> assert isclass(User)
    >>> assert issubclass(User, Account)
"""


Code 3.65. Solution
"""
* Assignment: OOP InheritancePatterns NoInheritance
* Complexity: easy
* Lines of code: 8 lines
* Time: 3 min

English:
    1. Create classes `MyAccount`, `Account`, `User`,  `Admin`
    2. Do not use inheritance
    3. Assignment demonstrates syntax, so do not add any attributes and methods
    4. Run doctests - all must succeed

Polish:
    1. Stwórz klasy `MyAccount`, `Account`, `User`,  `Admin`
    2. Nie używaj dziedziczenia
    3. Zadanie demonstruje składnię, nie dodawaj żadnych atrybutów i metod
    4. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass

    >>> assert isclass(Account)
    >>> assert isclass(User)
    >>> assert isclass(Admin)
    >>> assert isclass(MyAccount)
"""


Code 3.66. Solution
"""
* Assignment: OOP InheritancePatterns Multilevel
* Complexity: easy
* Lines of code: 8 lines
* Time: 3 min

English:
    1. Create class `MyAccount` from classes `Account`, `User`, `Admin`
    2. Use multilevel inheritance
    3. Assignment demonstrates syntax, so do not add any attributes and methods
    4. Run doctests - all must succeed

Polish:
    1. Stwórz klasę `MyAccount` z klas `Account`, `User`, `Admin`
    2. Użyj wielopoziomowego dziedziczenia
    3. Zadanie demonstruje składnię, nie dodawaj żadnych atrybutów i metod
    4. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass

    >>> assert isclass(Account)
    >>> assert isclass(User)
    >>> assert isclass(Admin)
    >>> assert isclass(MyAccount)
    >>> assert issubclass(MyAccount, Account)
    >>> assert issubclass(MyAccount, User)
    >>> assert issubclass(MyAccount, Admin)

    >>> assert issubclass(Account, object)
    >>> assert issubclass(User, Account)
    >>> assert issubclass(Admin, User)
    >>> assert issubclass(MyAccount, Admin)

    >>> assert len(Account.__subclasses__()) == 1
    >>> assert len(User.__subclasses__()) == 1
    >>> assert len(Admin.__subclasses__()) == 1
    >>> assert len(MyAccount.__subclasses__()) == 0
"""


Code 3.67. Solution
"""
* Assignment: OOP InheritancePatterns Multiple
* Complexity: easy
* Lines of code: 8 lines
* Time: 3 min

English:
    1. Create class `MyAccount` from classes `Account`, `User`, `Admin`
    2. Use mixins classes
    3. You can modify given classes
    4. Assignment demonstrates syntax, so do not add any attributes and methods
    5. Run doctests - all must succeed

Polish:
    1. Stwórz klasę `MyAccount` z klas `Account`, `User`, `Admin`
    2. Użyj klas domieszkowych (mixin)
    3. Możesz modyfikować dane klasy
    4. Zadanie demonstruje składnię, nie dodawaj żadnych atrybutów i metod
    5. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass

    >>> assert isclass(Account)
    >>> assert isclass(User)
    >>> assert isclass(Admin)
    >>> assert isclass(MyAccount)
    >>> assert issubclass(MyAccount, Account)
    >>> assert issubclass(MyAccount, User)
    >>> assert issubclass(MyAccount, Admin)

    >>> assert len(Account.__subclasses__()) == 1
    >>> assert len(User.__subclasses__()) == 1
    >>> assert len(Admin.__subclasses__()) == 1
    >>> assert len(MyAccount.__subclasses__()) == 0
"""