- rtshkmr's digital garden/
- Readings/
- Books/
- Fluent Python: Clear, Concise, and Effective Programming – Luciano Ramalho/
- Chapter 14. Inheritance: For Better or for Worse/
Chapter 14. Inheritance: For Better or for Worse
Table of Contents
Focus areas for this chapter:
- The super() function
- The pitfalls of subclassing from built-in types
- Multiple inheritance and method resolution order
- Mixin classes
chapter introduces multiple inheritance for those who have never used it, and provides some guidance on how to cope with single or multiple inheritance if you must use it.
What’s New in This Chapter #
The super() Function #
example use cases
when a subclass overrides a method of a superclass
and we want to let the superclass method do its job then add more logic to it
when we let the superclasses do their part in init fns
LANG_LIMITATION / IDIOM: unlike java constructor that automatically calls the nullary super constructor, python doesn’t do this so we need to ALWAYS manually write this in.
1 2 3def __init__(self, a, b) : super().__init__(a, b) ... # more initialization codeit will work (but not recommended) for us to hardcode the base class and call that base class’s function.
Also won’t work well with the multiple inheritance stuff
Subclassing Built-In Types Is Tricky #
Main takeaway:
Subclassing built-in types like dict or list or str directly is error-prone because the built-in methods mostly ignore user-defined overrides. Instead of subclassing the built-ins, derive your classes from the collections module using UserDict, UserList, and UserString, which are designed to be easily extended.
- it’s a flaw in method delegation within the C Language code of the builtin types (only affects classes derived directly from those types).
Major Caveat: bypassing behaviour
the code of the built-ins (written in C) usually does not call methods overridden by user-defined classes.
this applies for other dunder methods calling the overriden method.
using the overriding method directly is likely to work still.
This built-in behavior is a violation of a basic rule of object-oriented programming: the search for methods should always start from the class of the receiver (self), even when the call happens inside a method implemented in a superclass.
virtual vs nonvirtual methods
virtual: late-bound
non-virtual: bound at compile time
in python, every method is like latebound like a vritual method
builtins written in C seem to be nonvirtual by default (at least in CPython).
Multiple Inheritance and Method Resolution Order #
guiding question:
if we do multiple inheritance and both super classes have overlapping method names, how to make reference to the correct super function from the subclass
\(\implies\) this is the diamond problem and we wanna see how python solves this
2 factors that determined the activation sequences:
MRO of the leaf class
Goes all the way from current class all the way to the
objectclassDefines the activation order
use of
super()in each methodDetermines whether a particular method will be activated.
So if the method calls super() then we move to the next class in the MRO order and execute that.
How?
It’s not necessarily a BFS, it uses the C3 Algorithm (not important to understand unless need to wrangle complex hierarchies.)
MRO accounts for inheritance graph. Amongst siblings, it’s determined by the subclass declaration.
e.g.
Leaf(B, A),Leaf(A, B)are two different subclass declarations.Cooperative Methods: methods that call
super()Cooperative methods enable cooperative multiple inheritance. These terms are intentional: in order to work, multiple inheritance in Python requires the active cooperation of the methods involved.
GOTCHA: cooperative methods can be a cause of subtle bugs. \(\implies\) That’s why it is recommended that every method
mof a nonroot class should callsuper().m().
A noncooperative method can be the cause of subtle bugs.
Manycoders reading Example 14-4 may expect that when method A.pong calls super.pong(), that will ultimately activate Root.pong. But if B.pong is activated before, it drops the ball.
That’s why it is recommended that every method m of a nonroot class should call super().m().
Mixin Classes #
definition:
designed to be sub classed together with at least one other class as part of a multiple inheritance arrangement
won’t provide all the functionality of a concrete object
it’s supposed to be functionality mixins \(\implies\) customizes the behaviour of child or sibling classes.
so naturally will have some concrete methods implemented
are a convention that has no explicit language support in python/cpp
Mixins must appear first in the tuple of base classes in a class declaration
mixins typically depend on sibling classes that implements / inherits methods with the same signature
therefore, they must appear early in the MRO of a subclass that uses it
Case-Insensitive Mappings #
See this beautiful example
| |
Multiple Inheritance in the Real World #
Usage of multiple inheritance is not the norm in cpp or python really.
There are some known good use cases though.
ABCs Are Mixins Too #
Just like Java, we support multiple inheritance of interfaces
for those ABCs that have concrete methods, they play 2 roles:
they play the role of an interface definition
they also play the role of mixin classes
ThreadingMixIn and ForkingMixIn #
Django Generic Views Mixins #
the original generic views were functions so they were not extensible
have to start from scratch if we want to do something similar but not the same
Concrete subclasses of View are supposed to implement the handler methods, so why aren’t those methods part of the View interface? The reason: subclasses are free to implement just the handlers they want to support. A TemplateView is used only to display content, so it only implements get
reminder on why:
Great question! What you’re touching on is a common and important aspect of Python’s interface and subclassing philosophy, especially in frameworks like Django (which uses **View** classes). ### In Python, do you have to implement *all* methods of an interface/abstract base class? **Short answer:** **Not necessarily.** Unlike some statically typed languages with strict interface contracts, Python’s interfaces and abstract base classes (ABCs) often allow (or even expect) subclasses to implement only the parts of the interface that make sense for them. ### Why? 1. **Python’s dynamic and flexible nature** - Python favors *duck typing* and optional implementation over strict enforcement. - ABCs in Python can define optional methods, or provide default no-op or generic implementations in base classes. 2. **Partial method implementation is common in frameworks** - Consider a `View` base class that defines many “handler methods” like `get()`, `post()`, `put()`, etc. - Concrete subclasses (e.g., `TemplateView`, `CreateView`) implement only the handlers relevant to the use case. - This lets you write minimal classes that do exactly what you need, without burden. 3. **Interface methods can be *optional* or *protocol-like*** - Instead of forcing every subclass to implement an entire interface, the base class or the framework’s machinery calls a method *if it exists*. - If the method is not implemented, either a default behavior occurs, or a runtime error only arises if the method is actually called. 4. **Explicit design: selective handler implementation** - The `TemplateView` subclass only implements the `get()` method because it only responds to HTTP GET requests. - If you called `post()` on a `TemplateView`, it might raise a `NotImplementedError` or return a “method not allowed” response. - This selective implementation is a common design pattern in web frameworks. ### Contrast to stricter languages or interfaces In languages like Java or C#, interfaces typically require **all** declared methods to be implemented. Python’s philosophy is more permissive: - **You can think of many interfaces or ABCs in Python as “soft” contracts.** - **You implement the “part you use.”** - This works well because Python’s runtime dispatch is dynamic: calls to methods that don’t exist will fail only if you exercise them. ### Summary - In Python, you **do not always have to implement every method** defined by an interface or abstract base class. - Interfaces or base classes often define *optional* or *selective* methods. - Subclasses implement only the methods relevant to their behavior. - This design promotes flexibility and simplicity in code, especially in toolkits like Django where views handle different HTTP methods. - Calling a method that’s not implemented typically leads to a runtime error only if that method is invoked. If you want, I can provide a small Python example illustrating this partial implementation pattern (e.g., a base class with several methods, and subclasses implementing only some of them). Would that be helpful?It does take some time to learn how to leverage class-based views and how to extend them to fulfill specific application needs, but I found that it was worthwhile to study them.
Value that the mixins give for Django:
They eliminate a lot of boilerplate code, make it easier to reuse solutions, and even improve team communication—for example, by defining standard names to templates, and to the variables passed to template contexts. Class-based views are Django views “on rails.”
Multiple Inheritance in Tkinter #
the class heirarchy SHOULD NOT be very deep
usually it’s around 3 or 4 levels of concrete classes
GUI toolkits are where inheritance is most useful. The hierarchies can get really deep in them.
⭐️ Coping with Inheritance #
These are the rules of thumb that we need to rely on.
We have to do so because there’s no general theory about inheritance that can guide us against creating incomprehensible, brittle designs.
Favor Object Composition over Class Inheritance #
do composition and delegation
it can even replace the use of mixins and make behaviours available to different classes.
subclassing is a form of tight coupling and tall inheritance trees tend to be brittle.
Understand Why Inheritance Is Used in Each Case #
Reasons FOR using inheritance:
creates a subtype, so it’s a is-a relationship best done with ABCs
avoids code duplication by reuse, Mixins are useful for this too
The realisation here is that to prevent code reuse, inheritance is only an implementation detail, we can do composition & delegation too. However, interface inheritance is separate matter.
Make Interfaces Explicit with ABCs #
- Multiple inheritance of ABCs is not problematic.
- An ABC should subclass only abc.ABC or other ABCs.
- if a class is intended to define an interface, it should be an explicit ABC or a typing.Protocol subclass.
Use Explicit Mixins for Code Reuse #
- for reuse by multiple unrelated subclasses, without implying an “is-a” relationship,
- not to be instantatied
- since there’s no formal convention, try to Suffix the mixin name with
Mixin
Provide Aggregate Classes to Users #
A class that is constructed primarily by inheriting from mixins and does not add its own structure or behavior is called an aggregate class.
group together combinations of ABCs or mixins
we can now just use the aggregate class without having to figure out in which order they should be declared to work as intended.
typically just has an empty body (with docstring / pass)
Subclass Only Classes Designed for Subclassing #
some superclass methods may ignore the subclass overrides in unexpected ways.
\(\implies\) we should subclass only those that are intended to be extended.
how to check?
see the docs, if it’s a base class named, that hints at it
the docs will also indicate which of the methods are intended to be overridden.
see if the
@finaldecorator exists on the method (then it’s not intended for extension by overriding that method)
Avoid Subclassing from Concrete Classes #
if you do this, any internal state within a concrete class might get corrupted
even if we coorperate by calling
super(), there’s still many ways bugs can be introducedIf you must use subclassing for code reuse, then the code intended for reuse should be in mixin methods of ABCs or in explicitly named mixin classes.
Tkinter: The Good, the Bad, and the Ugly #
Chapter Summary #
Further Reading #
Smalltalk has traits which are a language construct that serves the role that a mixin class does, while avoiding some of the issues with multiple inheritance.
Scala also has traits.
while working as an application developer, you find yourself building multilevel class hierarchies, it’s likely that one or more of the following applies:
You are reinventing the wheel. Go look for a framework or library that provides
components you can reuse in your application.
You are using a badly designed framework. Go look for an alternative.
You are overengineering. Remember the KISS principle.
You became bored coding applications and decided to start a new framework.
Congratulations and good luck!