10.6. OOP Inheritance

10.6.1. Rationale

  • Child inherits all fields and methods from parent

  • Used to avoid code duplication

parent
superclass
base class

Class from other classes inherits

child
subclass

Class which inherits from parent

overload

When child has method or attribute with the same name as parent. In such case child attribute will be used (will overload parent).

10.6.2. Syntax

>>> class Parent:
...     def say_hello(self):
...         return 'Hello'
>>>
>>> class Child(Parent):
...     pass
>>>
>>>
>>> obj = Child()
>>> obj.say_hello()
'Hello'

10.6.3. Simple Inheritance

>>> class Engineer:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
>>>
>>> class Astronaut(Engineer):
...     pass
>>>
>>> class Cosmonaut(Engineer):
...     pass
>>>
>>>
>>> mark = Astronaut('Mark', 'Watney')
>>> ivan = Cosmonaut('Ivan', 'Ivanovic')

10.6.4. Multilevel Inheritance

>>> class Engineer:
...     pass
>>>
>>> class Pilot(Engineer):
...     pass
>>>
>>> class Astronaut(Pilot):
...     pass
>>>
>>>
>>> watney = Astronaut()
>>> type(watney)
<class 'Astronaut'>
>>> isinstance(watney, Engineer)
True
>>> isinstance(watney, Pilot)
True
>>> isinstance(watney, Astronaut)
True

10.6.5. Multiple Inheritance

>>> class Engineer:
...     pass
>>>
>>> class Pilot:
...     pass
>>>
>>> class Astronaut(Engineer, Pilot):
...     pass
>>>
>>>
>>> watney = Astronaut()
>>> type(watney)
<class 'Astronaut'>
>>> isinstance(watney, Engineer)
True
>>> isinstance(watney, Pilot)
True
>>> isinstance(watney, Astronaut)
True

10.6.6. Overload

>>> class A:
...     def show(self):
...         return 'a'
>>>
>>> class B(A):
...     pass
>>>
>>>
>>> obj = B()
>>> obj.show()
'a'
>>>
>>>
>>> class A:
...     def show(self):
...         return 'a'
>>>
>>> class B(A):
...     def show(self):
...         return 'b'
>>>
>>>
>>> obj = B()
>>> obj.show()
'b'
>>> class Person:
...     lastname = 'Watney'
...
...     def hello(self):
...         print(f'Hello {self.firstname} {self.lastname}')
>>>
>>>
>>> class Astronaut(Person):
...     firstname = 'Mark'
>>>
>>>
>>> a = Astronaut()
>>> a.hello()
Hello Mark Watney

10.6.7. Super Function

>>> class A:
...     def show(self):
...         return 'a'
>>>
>>> class B(A):
...     def show(self):
...         old_value = super().show()
...         return old_value + 'b'
>>>
>>>
>>> obj = B()
>>> obj.show()
'ab'
>>> class Engineer:
...     def __init__(self):
...         self.education = 'Engineer'
...         self.profession = 'Engineer'
>>>
>>> class Astronaut(Engineer):
...     def __init__(self):
...         super().__init__()
...         self.profession = 'Astronaut'
>>>
>>>
>>> mark = Astronaut()
>>>
>>> print(mark.__dict__)  # doctest: +NORMALIZE_WHITESPACE
{'education': 'Engineer',
 'profession': 'Astronaut'}
>>> class Engineer:
...     def __init__(self):
...         self.education = 'Engineer'
...         self.profession = 'Engineer'
>>>
>>> class Astronaut(Engineer):
...     def __init__(self):
...         self.profession = 'Astronaut'
...         super().__init__()
>>>
>>>
>>> mark = Astronaut()
>>>
>>> print(mark.__dict__)  # doctest: +NORMALIZE_WHITESPACE
{'profession': 'Engineer',
 'education': 'Engineer'}
>>> class Engineer:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...         self.education = 'Engineer'
...         self.profession = 'Engineer'
>>>
>>> class Astronaut(Engineer):
...     def __init__(self, firstname, lastname):
...         super().__init__(firstname, lastname)
...         self.profession = 'Astronaut'
>>>
>>>
>>> mark = Astronaut('Mark', 'Watney')
>>>
>>> print(mark.__dict__)  # doctest: +NORMALIZE_WHITESPACE
{'firstname': 'Mark',
 'lastname': 'Watney',
 'education': 'Engineer',
 'profession': 'Astronaut'}

10.6.8. Inheritance vs Composition

>>> class Car:
...     def engine_start(self):
...         print('Starting engine...')
>>>
>>>
>>> class Truck:
...     def engine_start(self):
...         print('Starting engine...')

Simple Inheritance:

>>> class Vehicle:
...     def engine_start(self):
...         print('Starting engine...')
>>>
>>>
>>> class Car(Vehicle):
...     pass
>>>
>>> class Truck(Vehicle):
...     pass

Inheritance Problem:

>>> class Vehicle:
...     def engine_start(self):
...         print('Starting engine...')
>>>
>>>
>>> class Car(Vehicle):
...     def windows_open(self):
...         print('Opening windows...')
>>>
>>>
>>> class Truck(Vehicle):
...     def windows_open(self):
...         print('Opening windows...')
>>>
>>>
>>> class Motorcycle(Vehicle):
...     pass

Not Implemented Error:

>>> class Vehicle:
...     def engine_start(self):
...         print('Starting engine...')
...
...     def windows_open():
...         print('Opening windows...')
>>>
>>>
>>> class Car(Vehicle):
...     pass
>>>
>>> class Truck(Vehicle):
...     pass
>>>
>>> class Motorcycle(Vehicle):
...     def windows_open(self):
...         raise NotImplementedError('Has no windows')

Composition:

>>> class Vehicle:
...     def engine_start(self):
...         print('Starting engine...')
...
...     def engine_stop():
...         print('Stopping engine...')
>>>
>>>
>>> class HasWindows:
...     def windows_open(self):
...         print('Opening windows...')
>>>
>>>
>>> class Car(Vehicle, HasWindows):
...     pass
>>>
>>> class Truck(Vehicle, HasWindows):
...     pass
>>>
>>> class Motorcycle(Vehicle):
...     pass

10.6.9. Use Cases

>>> class Iris:
...     def __init__(self, sepal_length, sepal_width,
...                  petal_length, petal_width, species):
...
...         self.sepal_length = sepal_length
...         self.sepal_width = sepal_width
...         self.petal_length = petal_length
...         self.petal_width = petal_width
...         self.species = species
>>>
>>>
>>> class Setosa(Iris):
...     pass
>>>
>>> class Versicolor(Iris):
...     pass
>>>
>>> class Virginica(Iris):
...     pass
>>>
>>>
>>> setosa = Setosa(
...     sepal_length=5.1,
...     sepal_width=3.5,
...     petal_length=1.4,
...     petal_width=0.2,
...     species='setosa')

10.6.10. Assignments

Code 10.11. Solution
"""
* Assignment: OOP Inheritance Simple
* Complexity: easy
* Lines of code: 4 lines
* Time: 3 min

English:
    1. Use data from "Given" section (see below)
    2. Create class `Woman` which inherits from `Venus`
    3. Create class `Man` which inherits from `Mars`

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Stwórz klasę `Woman`, która dziedziczy po `Venus`
    3. Stwórz klasę `Man`, która dziedziczy po `Mars`

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

    >>> assert isclass(Venus)
    >>> assert isclass(Woman)
    >>> assert isclass(Mars)
    >>> assert isclass(Man)
    >>> assert issubclass(Woman, Venus)
    >>> assert issubclass(Man, Mars)
"""


# Given
class Venus:
    pass


class Mars:
    pass


Code 10.12. Solution
"""
* Assignment: OOP Inheritance Multiple
* Complexity: easy
* Lines of code: 2 lines
* Time: 2 min

English:
    1. Use data from "Given" section (see below)
    2. Create class `Astronaut` which inherits from all of those classes
    3. Compare result with "Tests" section (see below)

Polish:
    1. Use data from "Given" section (see below)
    2. Stwórz klasę `Astronaut`, która dziedziczy po tych wszystkich klasach
    3. Porównaj wyniki z sekcją "Tests" (patrz poniżej)

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

    >>> assert isclass(Scientist)
    >>> assert isclass(Engineer)
    >>> assert isclass(Pilot)
    >>> assert isclass(MedicalDoctor)
    >>> assert isclass(Astronaut)
    >>> assert issubclass(Astronaut, Scientist)
    >>> assert issubclass(Astronaut, Engineer)
    >>> assert issubclass(Astronaut, Pilot)
    >>> assert issubclass(Astronaut, MedicalDoctor)
"""


# Given
class Scientist:
    pass


class Engineer:
    pass


class Pilot:
    pass


class MedicalDoctor:
    pass


Code 10.13. Solution
"""
* Assignment: OOP Inheritance Super
* Complexity: easy
* Lines of code: 6 lines
* Time: 5 min

English:
    1. Use data from "Given" section (see below)
    2. Create class `Astronaut` which inherits from `Person`
    3. Class `Astronaut` takes two arguments `name` and `mission`
    4. Set attribute `mission` in `Astronaut` inicializer method
    5. Call initializer method of `Person` passing `name` as an argument
    6. Define method `show()` returning name and after coma - a mission name
    6. Compare result with "Tests" section (see below)

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Stwórz klasę `Astronaut` dziedziczącą po `Person`
    3. Klasa `Astronaut` przyjmuje dwa argumenty `name` i `mission`
    4. Ustaw atrybut `mission` w metodzie inicjalizacyjnej w `Astronaut`
    5. Wywołaj metodę inicjalizacyjną z `Person` podając `name` jako argument
    6. Zdefiniuj metodę `show()` zwracającą imię i po przecinku - nazwę misji
    7. Porównaj wyniki z sekcją "Tests" (patrz poniżej)

Tests:
    >>> watney = Astronaut('Watney', 'Ares 3')
    >>> watney.show()
    'Watney, Ares 3'
    >>> lewis = Astronaut('Lewis', 'Ares 3')
    >>> lewis.show()
    'Lewis, Ares 3'
"""


# Given
class Person:
    def __init__(self, name):
        self.name = name