Python 中的 Mixin
Mixin 是 OOP 中的一种工具,一个用于传递一种特定行为的容器,多个类可以共享该行为,而无需建立“ is-a”(父子)关系,同时保持它们之间的松耦合
Python 依赖多重继承作为底层机制来实现 Mixin
Python 的 mixin 是具有特殊含义的普通类,和普通类之间没有语法差异——区别纯粹是语义上的
继承的缺点
过度泛化:当基类的范围变得过于宽泛时,子类就会开始继承不必要的特性
Erlang的创建者乔·阿姆斯特朗在接受Coders at Work的采访时,巧妙地描述了这个问题:
因为面向对象语言的问题在于,它们自带了各种隐式环境。你想要一根香蕉,结果得到的却是一只叼着香蕉的大猩猩和整片丛林
—乔·阿姆斯特朗
更好的方法是根据类的作用而不是其本身来设计类
这意味着优先考虑组合而不是继承,并将职责委托给更小、更专注的组件
mixin 是一把双刃剑
特点
class SerializableMixin:def serialize(self) -> dict:if hasattr(self, "__slots__"):return {name: getattr(self, name)for name in self.__slots__}else:return vars(self)
- 命名规范:并非强制要求,类名以
Mixin
结尾 - 普通类:由于 mixin 旨在提供独立、可复用的功能(而非强制继承结构),因此它们通常没有任何父类。这最大限度地减少了它们的作用域,并降低了在多重继承场景中名称解析错误的可能性
万事无绝对,有时也可能相反,将各个 Mixin 共同的部分收集到一个父类中可能有助于维护,如 Django 提供的 PermissionRequiredMixin
与 LoginRequiredMixin
,都继承AccessMixin
- 单一职责:一个 mixin 类只有一个职责
- 无状态性: Mixin 很少定义自己的构造函数
__init__()
或实例属性,这使得它们可以通过多重继承安全地与其他类集成,而不会发生冲突。Mixin 类封装的行为仅依赖于外部状态。Mixin 通常依赖于其混合到的类的属性。 - 非独立实体: Mixin 旨在为其他类添加新的或修改的行为。它们通常独立存在时没有任何意义,因此几乎不会直接实例化 mixin 类
Mixin 单独使用没有作用:
import json
class JSONSerializableMixin:def as_json(self) -> str:return json.dumps(vars(self))>>> mixin_instance = JSONSerializableMixin()
>>> mixin_instance.as_json()
'{}'
只有继承之后会发挥作用:
from dataclasses import dataclass@dataclass
class User(JSONSerializableMixin):user_id: intemail: str>>> User(555, "jdoe@example.com").as_json()
'{"user_id": 555, "email": "jdoe@example.com"}'
继承🆚Mixin
继承通常可以从两个方面理解:
- 实现代码复用
- 作为建立子类型关系的工具
这两种用法也分别称为实现继承和接口继承
mixin 主要侧重于代码复用,而非强“is a”关系
继承自 mixin 的类不会成为其子类型,只是使用其功能。
由于 mixin 并非旨在实现多态性,因此将其视为可与其基类互换通常违反了里氏代换原则
抽象类🆚Mixin
相同点:二者都是基础元素,旨在通过继承与一个或多个目标类组合
不同点:抽象类旨在被子类化并以多态方式使用。它们通常具有父类
此外,声明 ABC 需要使用特殊的元类或装饰器,Python 可以利用它们运行额外的检查:
from abc import ABC, abstractmethod
from typing import Anyclass Serializer(ABC):@abstractmethoddef serialize(self, data: Any) -> str:pass>>> abc_instance = Serializer()
TypeError: Can't instantiate abstract class Serializer
⮑ without an implementation for abstract method 'serialize'
使用抽象基类来定义算法的核心实现,同时在子类之间强制使用通用接口。这使得抽象基类 (ABC) 特别适合实现模板方法模式
Mixin 继承顺序
利用 Django 提供的 Mixin 与 ListView
实现一个图书列表页面:
class BookListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
不必严格依赖 MRO 就能利用好 mixin 。mixin 在与其他基类一起使用时才能发挥最大作用,但这有时会导致一些意想不到的行为
常见问题
- 过度使用:一个类继承的 Mixin 不要过多,功能繁杂可能导致其成为上帝对象
- 继承顺序错误:由于 Python 采用 C3 超类线性化算法决定 MRO( 靠左优先的类继承),需处理好 mixin 的继承顺序,否则不同 mixin 功能可能有冲突、重叠,甚至相互抵消
未完待续