Don’t forget that more often than not, a function is all you need. - Hynek Schlawack
You are here because you came to the conclusion that you need classes. At one point you notice that some parts of these classes look similar. Now you remember, that you were told in school that code duplication is bad.
Later you notice that maybe it’s not the implementation that is similar, but the way you use those classes. In this page we try to find ways to explain in the code how classes are connected. Let’s begin with the age-old question.
Think of inheritance as an is a relationship. Composition describes a has-a relationship.
Once you get comfortable with inheritance, it’s too easy to overuse it. Placing objects in a neat hierarchy appeals to our sense of order; programmers do it just for fun. - Fluent Python
Favor object composition over class inheritance.
When it comes to reading clarity, properly-done composition is superior to inheritance. Since code is much more often read than written, avoid subclassing in general, but especially don’t mix the various types of inheritance, and don’t use subclassing for code sharing. - Hynek Schlawack, Subclassing in Python Redux
Especially during initial design. Finding commonality between classes is easier than creating a family tree. It’s easier to describe what an object can do (has-a) than extend what it is (is-a). This flexibility is useful during incremental development.
Evidence is that some languages just straight up do not have it (inheritance), and they are perfectly usable. - source
Reminder: what is the purpose of creating abstractions
(classes) ? Hierarchical relationship can give us good
constraints (but remember, there is no final
keyword
in python), and the comfortable feeling that things are
organised. But the ultimate goal is to manage complexity. We
achieve this with hiding complexity behind useful abstractions.
Inheritance may represent relationships of objects that live in
the real world (or other domains like mathematics) but this
doesn’t necesseraily mean that it’s useful in the world of our
program.
So again inheritance describes an is-a relationship. But there is something more that we can expect from inheritance.
Subclasses should be substitutable for their base classes. And the program should still run correctly.
Square is not a rectangle, unless it’s immutable - Uncle Bob podcast probably
What happens if you can set width and height independently?
Model your classes based on behaviour
This is what we should always do in Python, objects are defined by their interface. How can we still verify that “correctness”? Easier to ask forgiveness than permission.
Now let’s see what we can do with inheritance.
Composition disguised as inheritance.
A mixin class is designed to be subclassed together with at least one other class in a multiple inheritance arrangement. A mixin is not supposed to be the only base class of a concrete class, because it does not provide all the functionality for a concrete object, but only adds or customizes the behavior of child or sibling classes. - Fluent Python
class PlainPizza:
def __init__(self):
self.toppings = []
class OlivesMixin:
def add_olives(self):
print("Adding olives!")
self.toppings += ['olives']
class SistersPizza(OlivesMixin, PlainPizza):
def prepare_pizza(self):
self.add_olives()
Avoid mixins when you expect only one combination.
Abstract base classes complement duck-typing by providing a way to define interfaces when other techniques like hasattr() would be clumsy or subtly wrong (for example with magic methods). - Python documentation
Read more about the Problem with
isinstance
import abc
class Pizza(abc.ABC):
@abc.abstractmethod
def prepare_pizza(self, data):
"""Do something"""
But Pizza
is probably not an ABC.
A mixin provides functionality, but is unable to directly use it. A user is intended to use it through a (sub)class. An abstract base class provides an interface, but without usable functionality. A user is intended to create the functionality called by the interface.
ABC:
Mixin:
Avoid this. When you override methods, internal state can be corrupted.
All non-leaf classes should be abstract - Scott Meyer: More Effective C++
In the standard library documentation explicitly states if a classes is designed to be subclassed.
Python 3.8< supports the @final
decorator,
read more
Excellent, must-read article: The Composition Over Inheritance Principle from Brandon Rhodes.
A crucial weakness of inheritance as a design strategy is that a class often needs to be specialized along several different design axes at once