6.8. Inheritance Recap
6.8.1. No Inheritance
>>> class User:
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
...
... def to_pickle(self):
... import pickle
... data = vars(self)
... return pickle.dumps(data)
...
... def to_json(self):
... import json
... data = vars(self)
... return json.dumps(data)
>>> myuser = User('Alice', 'Apricot')
>>>
>>> print(myuser.to_json())
{"firstname": "Alice", "lastname": "Apricot"}
>>>
>>> print(myuser.to_pickle())
b'\x80\x05\x95.\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\tfirstname\x94\x8c\x05Alice\x94\x8c\x08lastname\x94\x8c\x07Apricot\x94u.'
This class contains methods, which could be also used by other classes, this will lower the amount of code to maintain. So we refactor and Extract superclass.
6.8.2. Single Inheritance
>>> class Serialize:
... def to_pickle(self):
... import pickle
... data = vars(self)
... return pickle.dumps(data)
...
... def to_json(self):
... import json
... data = vars(self)
... return json.dumps(data)
>>>
>>>
>>> class User(Serialize):
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>> myuser = User('Alice', 'Apricot')
>>>
>>> print(myuser.to_json())
{"firstname": "Alice", "lastname": "Apricot"}
>>>
>>> print(myuser.to_pickle())
b'\x80\x05\x95.\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\tfirstname\x94\x8c\x05Alice\x94\x8c\x08lastname\x94\x8c\x07Apricot\x94u.'
It's better. Now we can reuse Serialize
class. However... Is that true,
that each class can be serialized to JSON and Pickle at the same time?
6.8.3. Linear Inheritance
We can improve code by splitting those capabilities into separate classes. In this case, the Multi level inheritance is a bad pattern here:
>>> class ToJSON:
... def to_json(self):
... import json
... data = vars(self)
... return json.dumps(data)
>>>
>>> class ToPickle(ToJSON):
... def to_pickle(self):
... import pickle
... data = vars(self)
... return pickle.dumps(data)
>>>
>>>
>>> class User(ToPickle):
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>> myuser = User('Alice', 'Apricot')
>>>
>>> print(myuser.to_json())
{"firstname": "Alice", "lastname": "Apricot"}
>>>
>>> print(myuser.to_pickle())
b'\x80\x05\x95.\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\tfirstname\x94\x8c\x05Alice\x94\x8c\x08lastname\x94\x8c\x07Apricot\x94u.'
It will work as intended for the end-user, but the code structure is disturbed. Not all classes which are serialized to Pickle, are also serialized to JSON. In out case it's a must. This kind of Multi-level inheritance could be found in languages which does not support Multiple inheritance.
6.8.4. Composition
Java is such language which does not have Multiple inheritance. In that case, developers are not using inheritance, and they even go to the extreme, by considering inheritance a bad practice. They use composition:
>>> class ToJSON:
... def to_json(self):
... import json
... data = {attrname: attrvalue
... for attrname, attrvalue in vars(self).items()
... if not attrname.startswith('_')}
... return json.dumps(data)
>>>
>>> class ToPickle:
... def to_pickle(self):
... import pickle
... data = {attrname: attrvalue
... for attrname, attrvalue in vars(self).items()
... if not attrname.startswith('_')}
... return pickle.dumps(data)
>>>
>>>
>>> class User:
... firstname: str
... lastname: str
... __json_serializer: ToJSON
... __pickle_serializer: ToPickle
...
... def __init__(self, firstname, lastname, json_serializer=ToJSON, pickle_serializer=ToPickle):
... self.firstname = firstname
... self.lastname = lastname
... self.__json_serializer = json_serializer
... self.__pickle_serializer = pickle_serializer
...
... def to_json(self):
... return self.__json_serializer.to_json(self)
...
... def to_pickle(self):
... return self.__pickle_serializer.to_pickle(self)
>>> myuser = User('Alice', 'Apricot')
>>>
>>> print(myuser.to_json())
{"firstname": "Alice", "lastname": "Apricot"}
>>>
>>> print(myuser.to_pickle())
b'\x80\x05\x95.\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\tfirstname\x94\x8c\x05Alice\x94\x8c\x08lastname\x94\x8c\x07Apricot\x94u.'
It gives me ability to write something better:
>>> class MyBetterSerializer(ToJSON):
... def to_json(self):
... return ...
>>>
>>> myuser = User('Alice', 'Apricot', json_serializer=MyBetterSerializer)
This work as intended, and nothing changed for the end-user. This maybe a good pattern for Java, but for Python ecosystem is over-engineered (to complex for that particular usecase).
6.8.5. Multiple Inheritance
That was a must, because Java don't have Multiple inheritance and Simple inheritance or Linear inheritance was a bad idea. In Python there is Multiple inheritance capability which enables to create a small and specialized classes and mix them together in order to create objects. Those are called Mixin classes and they use multiple inheritance mechanism:
>>> class ToJSON:
... def to_json(self):
... import json
... data = vars(self)
... return json.dumps(data)
>>>
>>> class ToPickle:
... def to_pickle(self):
... import pickle
... data = vars(self)
... return pickle.dumps(data)
>>>
>>>
>>> class User(ToJSON, ToPickle):
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>> myuser = User('Alice', 'Apricot')
>>>
>>> print(myuser.to_json())
{"firstname": "Alice", "lastname": "Apricot"}
>>>
>>> print(myuser.to_pickle())
b'\x80\x05\x95.\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\tfirstname\x94\x8c\x05Alice\x94\x8c\x08lastname\x94\x8c\x07Apricot\x94u.'
6.8.6. Assignments
# %% About
# - Name: Inheritance Mixin Decompose
# - Difficulty: easy
# - Lines: 30
# - Minutes: 5
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% English
# 1. Refactor class `Hero` to use multiple inheritance
# 2. Name mixin classes: `HealthMixin` and `PositionMixin`
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zrefaktoruj klasę `Hero` aby użyć wielodziedziczenia
# 2. Nazwij klasy domieszkowe: `HealthMixin` i `PositionMixin`
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from random import seed; seed(0)
>>> from inspect import isclass
>>> assert isclass(Hero)
>>> assert isclass(HealthMixin)
>>> assert isclass(PositionMixin)
>>> assert issubclass(Hero, HealthMixin)
>>> assert issubclass(Hero, PositionMixin)
>>> assert hasattr(HealthMixin, 'health')
>>> assert hasattr(HealthMixin, 'is_alive')
>>> assert hasattr(HealthMixin, 'is_dead')
>>> assert hasattr(PositionMixin, 'position_x')
>>> assert hasattr(PositionMixin, 'position_y')
>>> assert hasattr(PositionMixin, 'position_set')
>>> assert hasattr(PositionMixin, 'position_change')
>>> assert hasattr(PositionMixin, 'position_get')
>>> assert hasattr(Hero, 'HEALTH_MIN')
>>> assert hasattr(Hero, 'HEALTH_MAX')
>>> assert hasattr(Hero, 'health')
>>> assert hasattr(Hero, 'position_x')
>>> assert hasattr(Hero, 'position_y')
>>> assert hasattr(Hero, 'is_alive')
>>> assert hasattr(Hero, 'is_dead')
>>> assert hasattr(Hero, 'position_set')
>>> assert hasattr(Hero, 'position_change')
>>> assert hasattr(Hero, 'position_get')
>>> watney = Hero()
>>> watney.is_alive()
True
>>> watney.position_set(x=1, y=2)
>>> watney.position_change(left=1, up=2)
>>> watney.position_get()
(0, 0)
>>> watney.position_change(right=1, down=2)
>>> watney.position_get()
(1, 2)
"""
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`
# %% Imports
from typing import ClassVar
from dataclasses import dataclass
from random import randint
# %% Types
HealthMixin: type
PositionMixin: type
Hero: type
# %% Data
# %% Result
@dataclass
class Hero:
HEALTH_MIN: ClassVar[int] = 10
HEALTH_MAX: ClassVar[int] = 20
health: int = 0
position_x: int = 0
position_y: int = 0
def position_set(self, x: int, y: int) -> None:
self.position_x = x
self.position_y = y
def position_change(self, right=0, left=0, down=0, up=0):
x = self.position_x + right - left
y = self.position_y + down - up
self.position_set(x, y)
def position_get(self) -> tuple:
return self.position_x, self.position_y
def __post_init__(self) -> None:
self.health = randint(self.HEALTH_MIN, self.HEALTH_MAX)
def is_alive(self) -> bool:
return self.health > 0
def is_dead(self) -> bool:
return self.health <= 0