python-learn.png
Introduction
Once when using Python’s socketserver, I came across ForkingMixIn and ThreadingMixIn. I was fascinated by this plug-in style syntactic sugar. Recently, while writing my own code, I also wanted to create some of these plug-and-play plugin code snippets, so I explored Python’s mix-in mechanism.
Simply put, it is a technique that leverages multiple inheritance to stylishly enhance an original class by plugging in additional code snippets.
Author: 木鸟杂记 https://www.qtmuniao.com, please indicate the source when reprinting
Key Points
To get straight to the point, here are the conclusions. When using MixIn, there are just a few key points to keep in mind:
- A Mix-in class is essentially a code snippet and cannot survive on its own.
- Enhance functions with the same name in “sibling” [1] classes to achieve a plug-and-play effect.
- When defining a subclass, the base class inheritance order cannot be arbitrary; the MixIn class must come before the class being mixed into.
Code Snippet
Unlike static languages, Python uses dynamic binding. Therefore, at definition time, it can independently “exist” as a code snippet (but cannot “survive,” meaning it cannot be used to define class instances for binding), and can reference functions that do not exist in the snippet:
1 | class SetOnceMappingMixin: |
This code snippet obviously cannot be instantiated on its own, but defining it separately does no harm, and it assumes that the class being mixed into has __setitem__.
Multiple Inheritance Replacement
Since it is plug-and-play, the API of the class being mixed into [2] remains unchanged whether the Mixin is present or not. The principle behind this mechanism is using a function with the same name to replace the original function. Mixin emphasizes its role as a plugin—that is, adding extra functionality on top of the original function implementation. To achieve this, the original function must be reused. In Python, the function that serves this purpose is super().
In programming languages that only allow single inheritance, such as Java, super() is unambiguously a reference to the parent class. But for Python, which supports multiple inheritance, where super() ultimately points to requires some arrangement. Consider the following example:
1 | class Base: |
You can guess what the output will be, then comment out #1 and #2 to see what the output is.
The reason is that for each class, Python computes a Method Resolution Order (MRO) list. The super().func() function traverses this list from front to back, finding the first class that has the func function and then calling it. The rule for constructing the MRO list is quite simple; loosely speaking, it is from left to right, from bottom to top.
Therefore, the MRO list for class C is [3]:
1 | C.__mro__ |
However, if the class inheritance is extremely messy—for example, if a circular dependency occurs—an error will be raised when accessing C.__mro__: TypeError: Cannot create a consistent method resolution. I won’t go into detail here; interested readers can Google it themselves.
Returning to mix-in classes, with this understanding, it becomes clear why the mix-in class must be defined before the class being mixed into when defining a subclass: so that the mix-in class can call the function with the same name in the class being mixed into via the super() method.
A complete example [4] is as follows:
1 | class SetOnceMappingMixin: |
Notes
[1] The “sibling” relationship here refers to the relationship between two classes in multiple inheritance. For example, in class Child(A, B): pass, classes A and B are siblings.
[2] The class being mixed into is the “sibling” of the Mixin. For example, in class Strong(Week, PowerMixIn), Week is the class being mixed into for PowerMixin.
[3] Calling Methods on Parent Classes: https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p07_calling_method_on_parent_class.html
[4] Extending Classes with Mixins: https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p18_extending_classes_with_mixins.html
