5.9. 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.

5.9.1. No Inheritance

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

5.9.2. Single Inheritance

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

5.9.3. Multilevel Inheritance

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

5.9.4. Multiple Inheritance

  • Mother and Father are Mixin Classes

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

5.9.5. Composition

Static version:

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

Dynamic version:

>>> 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

5.9.6. Aggregation

Static version:

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

Dynamic version:

>>> class Mother:
...     pass
>>>
>>> class Father:
...     pass
>>>
>>>
>>> class Child:
...     parents: list[Mother|Father]
...
...     def __init__(self, mother=Mother(), father=Father()):
...         self.parents = []
...         self.parents.append(mother)
...         self.parents.append(father)

5.9.7. Why?

>>> 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())

5.9.8. Use Case - 0x01

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

5.9.9. Use Case - 0x02

>>> from json import JSONEncoder, JSONDecoder
>>>
>>>
>>> class User:
...     json_encoder: JSONEncoder
...     json_decoder: JSONDecoder
...
...     def __init__(self,
...                  json_encoder: JSONEncoder = JSONEncoder(),
...                  json_decoder: JSONDecoder = JSONDecoder(),
...                  ) -> None:
...         self.json_encoder = json_encoder
...         self.json_decoder = json_decoder
...
...     def json_encode(self, data):
...         self.json_encoder.encode(data)
...
...     def json_decoder(self, data):
...         self.json_decoder.decode(data)

5.9.10. Use Case - 0x03

>>> from datetime import date
>>> import json
>>> DATA = {'firstname': 'Mark', 'lastname': 'Watney'}
>>>
>>> json.dumps(DATA)
'{"firstname": "Mark", "lastname": "Watney"}'
>>> DATA = {'firstname': 'Mark', 'lastname': 'Watney', 'birthday': 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', 'birthday': date(1969, 7, 21)}
>>>
>>> json.dumps(DATA, cls=Encoder)
'{"firstname": "Mark", "lastname": "Watney", "birthday": "1969-07-21"}'

5.9.11. Further Reading

5.9.12. Assignments

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

English:
    1. Create classes `MarsMission`, `Habitat`, `Rocket`, `Astronaut`
    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 `MarsMission`, `Habitat`, `Rocket`, `Astronaut`
    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(Habitat)
    >>> assert isclass(Astronaut)
    >>> assert isclass(Rocket)
    >>> assert isclass(MarsMission)
"""


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

English:
    1. Create class `MarsMission` from classes `Habitat`, `Rocket`, `Astronaut`
    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ę `MarsMission` z klas `Habitat`, `Rocket`, `Astronaut`
    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(Habitat)
    >>> assert isclass(Astronaut)
    >>> assert isclass(Rocket)
    >>> assert isclass(MarsMission)
    >>> assert issubclass(MarsMission, Habitat)
    >>> assert issubclass(MarsMission, Astronaut)
    >>> assert issubclass(MarsMission, Rocket)

    >>> assert len(Habitat.__subclasses__()) == 1
    >>> assert len(Astronaut.__subclasses__()) == 1
    >>> assert len(Rocket.__subclasses__()) == 1
    >>> assert len(MarsMission.__subclasses__()) == 0
"""


Code 5.20. Solution
"""
* Assignment: OOP InheritancePatterns Composition
* Complexity: easy
* Lines of code: 10 lines
* Time: 3 min

English:
    1. Create class `MarsMission` from classes `Habitat`, `Rocket`, `Astronaut`
    2. Use composition
    3. Assignment demonstrates syntax, so do not add any attributes and methods (only type annotations)
    4. Run doctests - all must succeed

Polish:
    1. Stwórz klasę `MarsMission` z klas `Habitat`, `Rocket`, `Astronaut`
    2. Użyj kompozycji
    3. Zadanie demonstruje składnię, nie dodawaj żadnych atrybutów i metod (tylko anotacje typów)
    4. Uruchom doctesty - wszystkie muszą się powieść

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

    >>> assert isclass(Habitat)
    >>> assert isclass(Astronaut)
    >>> assert isclass(Rocket)
    >>> assert isclass(MarsMission)
    >>> assert MarsMission.__annotations__['habitat'] is Habitat
    >>> assert MarsMission.__annotations__['astronaut'] is Astronaut
    >>> assert MarsMission.__annotations__['rocket'] is Rocket
"""


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

English:
    1. Create class `MarsMission` from classes `Habitat`, `Rocket`, `Astronaut`
    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ę `MarsMission` z klas `Habitat`, `Rocket`, `Astronaut`
    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(Habitat)
    >>> assert isclass(Astronaut)
    >>> assert isclass(Rocket)
    >>> assert isclass(MarsMission)
    >>> assert issubclass(MarsMission, Habitat)
    >>> assert issubclass(MarsMission, Astronaut)
    >>> assert issubclass(MarsMission, Rocket)

    >>> assert len(Habitat.__subclasses__()) == 1
    >>> assert len(Astronaut.__subclasses__()) == 1
    >>> assert len(Rocket.__subclasses__()) == 1
    >>> assert len(MarsMission.__subclasses__()) == 0
"""