Inheritance, ABC and mixin classes

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.

Inheritance versus composition

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.

Liskov substitution principle

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.

Mixin

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

Example:

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.

ABC

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.

ABC vs Mixin

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:

Subclassing concrete classes

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

Avoiding subclass explosion

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

Read more

#python