multiple inheritance

#Multiple inheritance and MixIn mode in oop

Inheritance is the most commonly used code reuse method in oop (OOP), but once the classification dimensions increase, the shortcomings of single inheritance will be exposed. This article uses animal design as a clue to take you from the simplest inheritance to experience类爆炸The pain points eventually led to the best practices of Python multiple inheritance and MixIn mode.


1. Basic dilemma of inheritance

1.1 A simple single inheritance hierarchy is fine

Let's say we want to model four animals:

  • 🐶 Dog - dog
  • 🦇 Bat - bat
  • 🦜 Parrot - Parrot
  • 🦢 Ostrich - ostrich

If you prioritize by biological classification, the single inheritance structure is very clear:

classDiagram
    class Animal
    class Mammal
    class Bird
    class Dog
    class Bat
    class Parrot
    class Ostrich
    
    Animal <|-- Mammal
    Animal <|-- Bird
    Mammal <|-- Dog
    Mammal <|-- Bat
    Bird <|-- Parrot
    Bird <|-- Ostrich

All mammals inheritMammal, all birds inheritBird, well-organized and well maintained.

1.2 When behavioral classification is mixed in...

But the reality is that we not only care about what animals are biologically, but also what they can do: can they run or fly? Should we eat meat or grass? Multi-dimensional classification Once superimposed, single inheritance will be stretched.


2. "Classification explosion" of single inheritance

2.1 Try another dimension

If we don't focus on biological classification, but first look at behavior, for example, first divide it into "things that can run" and "things that can fly":

classDiagram
    class Animal
    class Runnable
    class Flyable
    class Dog
    class Ostrich
    class Parrot
    class Bat
    
    Animal <|-- Runnable
    Animal <|-- Flyable
    Runnable <|-- Dog
    Runnable <|-- Ostrich
    Flyable <|-- Parrot
    Flyable <|-- Bat

Here comes the problem: in this way, the "viviparous" characteristic common to mammals and the "oviparous" characteristic common to birds will be lost.AnimalAre they handled uniformly? Impossible, because dogs and bats are mammals and birds and cannot be placed under the same tree branch.

2.2 Two dimensions together: the number of classes explodes

What if we simply put biological type and behavioral type into an inheritance system at the same time?

classDiagram
    class Animal
    class Mammal
    class Bird
    class MRun
    class MFly
    class BRun
    class BFly
    class Dog
    class Bat
    class Ostrich
    class Parrot
    
    Animal <|-- Mammal
    Animal <|-- Bird
    Mammal <|-- MRun
    Mammal <|-- MFly
    Bird <|-- BRun
    Bird <|-- BFly
    MRun <|-- Dog
    MFly <|-- Bat
    BRun <|-- Ostrich
    BFly <|-- Parrot

In order to support "mammals + able to run", "mammals + able to fly", "birds + able to run", "birds + able to fly", we had to add 4 intermediate classes out of thin air. These are only two dimensions. What if we add dimensions such as "carnivorous/plant-eating" and "domesticated/wild"? The number of classes grows exponentially and the code quickly becomes unmaintainable.


3. Python’s multiple inheritance solution

Fortunately, Python is one of the few mainstream languages ​​that natively supports multiple inheritance, which can break this deadlock: retain a clear "core identity main inheritance line", and additional behavioral/functional dimensions are made into independent parent classes.

class Animal:
    """所有动物的基类"""
    pass

# 🧬 核心继承线:保留生物学特性
class Mammal(Animal):
    def reproduce(self):
        print("胎生哺乳")

class Bird(Animal):
    def reproduce(self):
        print("卵生")

# 🦾 行为/功能补充类
class Runnable:
    def run(self):
        print("四足或两足快速移动中...")

class Flyable:
    def fly(self):
        print("展开翅膀飞行中...")

# 🎯 具体动物类:先写「身份主继承」,再补「功能类」
class Dog(Mammal, Runnable):
    pass

class Bat(Mammal, Flyable):
    pass

class Parrot(Bird, Flyable):
    pass

class Ostrich(Bird, Runnable):
    pass

# 测试
dog = Dog()
dog.reproduce()  # 输出:胎生哺乳
dog.run()        # 输出:四足或两足快速移动中...

In this way, dogs are still mammals, but they also have the ability to run; bats are mammals, but they have the ability to fly. Clear main identity and free combination of functions.


4. Standardize into a pattern: MixIn

Although multiple inheritance is easy to use, it can easily become confusing when there are too many parent classes - problems such as method resolution order (MRO), conflict of methods with the same name, and blurred responsibility boundaries may arise at any time. For this reason, the MixIn (mixing) mode has been derived in the industry.

4.1 Core Rules of MixIn

  1. The main inheritance line must be single: there can only be one parent class representing the core identity/entity (e.g.MammalorBird)。
  2. MixIn classes only provide purely functional additions, they should not be instantiated individually, and generally do not hold a large amount of state.
  3. Name with unified suffix: commonly used by the Python communityMixInMixinorFeature, used uniformly hereMixIn
  4. Inheritance order: The main identity is always placed as the first parameter.

Rewrite the example just now according to the rules and add a new oneCarnivorousMixIn(Carnivorous function):

# 🧬 身份主继承线(不变)
class Animal:
    """所有动物的基类"""
    pass

class Mammal(Animal):
    def reproduce(self):
        print("胎生哺乳")

class Bird(Animal):
    def reproduce(self):
        print("卵生")

# 🦾 纯功能 MixIn
class RunnableMixIn:
    def run(self):
        print("移动中...")

class FlyableMixIn:
    def fly(self):
        print("飞行中...")

class CarnivorousMixIn:
    def eat_meat(self):
        print("津津有味吃🥩")

# 🎯 具体动物类
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

# 测试
dog = Dog()
dog.reproduce()  # 主身份优先
dog.run()        # MixIn 功能补充
dog.eat_meat()   # 新增 MixIn 功能

4.2 Does the order have any impact?

If methods with the same name are defined in the main identity class and the MixIn class, Python will determine the calling order according to the C3 linearization algorithm. As long as you always put the main identity class first, there's usually no problem. Keep one thing in mind for daily use: Start with the main identity, followed by MixIn.


5. MixIn instance of the standard library

The MixIn pattern is not just on paper, its shadow can be seen everywhere in the Python standard library. The most classic is undoubtedlysocketserverModule:

from socketserver import TCPServer, UDPServer, ThreadingMixIn, ForkingMixIn

# 身份主继承线:TCPServer(TCP 服务)
# MixIn 补充:ThreadingMixIn(多线程)
class ThreadedTCPServer(TCPServer, ThreadingMixIn):
    pass

# 身份主继承线:UDPServer(UDP 服务)
# MixIn 补充:ForkingMixIn(多进程)
class ForkedUDPServer(UDPServer, ForkingMixIn):
    pass

# 直接用就行,不需要自己重写多线程/多进程逻辑

This is the power of MixIn: Without modifying the code of the core service, you can quickly combine different service modes through just the mixing function.


6. MixIn enhancements for modern Python

In Python 3.5+, we can use Abstract Base Class (ABC) and Type Hints to make MixIn more standardized and safe.

6.1 Methods that need to be implemented to use ABC to constrain MixIn

from abc import ABC, abstractmethod

class FlyableMixIn(ABC):
    @abstractmethod   # 🚨 子类必须重写 fly()
    def fly(self):
        pass
    
    # 🦅 提供默认的起飞方法,具体飞行方式由子类决定
    def take_off(self):
        print("蓄力,展开翅膀!")
        self.fly()

class Bat(Mammal, FlyableMixIn):
    def fly(self):
        print("蝙蝠扑棱翅膀飞行中...")

# 可以放心调用 take_off()
bat = Bat()
bat.take_off()

hereFlyableMixIndeclares an abstract methodfly(), any subclass that inherits it cannot be instantiated if it does not implement it. This ensures complete functionality at the grammatical level.

6.2 Use Protocol to implement more flexible type checking

If you don't want to force ABC inheritance, you can usetyping.Protocol(Python 3.8+) Do structured type checking:

from typing import Protocol

# 🧩 Protocol:只要“长得像” Runnable 就行
class RunnableProtocol(Protocol):
    def run(self) -> None:
        ...

# RunnableMixIn 不需要显式继承 Protocol,但必须实现 run()
class RunnableMixIn:
    def run(self) -> None:
        print("快速移动~")

In this way, static type checking tools (such as mypy) will determine whether the types match based on the actual implementation, without having to rely heavily on inheritance relationships at runtime.


7. Don’t let MixIn “go crazy” – composition is better than inheritance

MixIn is flexible, but if too much functionality is mixed into a class (e.g. more than 3), the inheritance chain can become as elusive as "diamond inheritance". Even if Python's MRO could resolve conflicts, maintenance costs would skyrocket. At this time, the composition mode is a better choice: encapsulate the function into an independent object and use it as an attribute of the class instead of mixing it in through inheritance.

# 🏃 独立功能类
class Runner:
    def run(self):
        print("快速移动~")

class Flyer:
    def fly(self):
        print("飞行~")

# 🎯 组合实现
class Dog(Mammal):
    def __init__(self):
        self.runner = Runner()   # 将“跑的能力”委托给独立对象
    
    def run(self):
        self.runner.run()

The advantage of this way of writing is that the relationship is clear, the responsibilities are independent, and the functions can be replaced or tested at any time.


Summarize

  1. Single inheritance is very refreshing in a single dimension, but it can easily cause class explosion when faced with multiple dimensions (species + behavior + feeding habits...).
  2. Multiple inheritance is a solution given by Python, which can retain a main inheritance line and make the remaining functions into separate parent classes.
  3. MixIn pattern is an engineering specification for multiple inheritance:
  • Single primary identity,MixInOnly add functions, add a suffix to the name, and put the main identity first.
  1. Python standard library (such assocketserver) makes extensive use of MixIn mode, proving its usefulness.
  2. Modern Python can use ABC / Protocol to enhance MixIn’s constraints and type safety.
  3. When there are too many functions mixed in, combination is better than inheritance and the functions are separated into independent objects, making the design more flexible and easier to maintain.

MixIn is a sharp blade given to us by Python. Use it well to write highly reusable and clear code; but any sharp tool must be measured - flexible use and careful combination are the most elegant practices.