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 提供的 PermissionRequiredMixinLoginRequiredMixin,都继承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: int
    email: str

>>> User(555, "jdoe@example.com").as_json()
'{"user_id": 555, "email": "jdoe@example.com"}'

继承🆚Mixin

继承通常可以从两个方面理解:

  1. 实现代码复用
  2. 作为建立子类型关系的工具

这两种用法也分别称为实现继承接口继承

mixin 主要侧重于代码复用,而非强“is a”关系

继承自 mixin 的类不会成为其子类型,只是使用其功能。

由于 mixin 并非旨在实现多态性,因此将其视为可与其基类互换通常违反了里氏代换原则

抽象类🆚Mixin

相同点:二者都是基础元素,旨在通过继承与一个或多个目标类组合

不同点:抽象类旨在被子类化以多态方式使用。它们通常具有父类

此外,声明 ABC 需要使用特殊的元类装饰器,Python 可以利用它们运行额外的检查:

from abc import ABC, abstractmethod
from typing import Any

class Serializer(ABC):
    @abstractmethod
    def 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 在与其他基类一起使用时才能发挥最大作用,但这有时会导致一些意想不到的行为

常见问题

  1. 过度使用:一个类继承的 Mixin 不要过多,功能繁杂可能导致其成为上帝对象
  2. 继承顺序错误:由于 Python 采用 C3 超类线性化算法决定 MRO( 靠左优先的类继承),需处理好 mixin 的继承顺序,否则不同 mixin 功能可能有冲突、重叠,甚至相互抵消

未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值