Skip to main content

Python-Mixin 混合类

背景

在谈及混合类之前,首先需要了解 多重继承 的概念,多重继承是指一个类可以继承多个父类,这样就可以从多个父类中继承属性和方法,但是这种设计模式也存在一定的问题。

例如,轿车是一个交通工具,所以轿车类应该继承交通工具这个父类。那民航飞机呢?它也属于交通工具的一种,所以也应该继承交通工具这个父类,但是交通工具这个类应该怎么设计?是否应该实现飞行功能?如果实现,那轿车继承交通工具父类显然不合适,因为轿车根本没有飞行功能。如果不实现,民航飞机继承交通工具类也同样不合适。如果两者都分别实现自己的方法,那将会违背代码重用的原则,那应该这么解决这个问题?事实上,我们可以把地上跑的,天上飞的,甚至水上漂的这些工具的功能抽象出来实现交通工具这个父类。对于飞机来说,那就去继承交通工具和有飞行功能两个父类,对于船来说,那就去继承交通工具和有水上漂功能的两个父类。但是这样子的多重继承说到底还是违背了“is-a”的原则,这个问题应该怎么样处理?

is-a | has-a
  • is-a 是一种继承关系,A 是 B 的一种,比如在长方体和正方体之间 -- 正方体是长方体的一种
  • has-a 是一种组合关系,A 有 B,比如在人和手之间 -- 人有手,汽车有轮胎

再举一个实际工作的问题,如果我们需要实现一个日志器,我们同时需要他输出到屏幕和输出到文件中的两个功能,但是我们想做到自由组合,比如在开发环境中,我们只需要输出屏幕,在生产环境中,我们只需要输出到文件中,在测试环境中,我全都要,那该如何实现?

wqdx

用混合类可以比较巧妙的解决这个问题,混合类是一种特殊的类,它不是一个真正的类,它只是一个类的集合,它的作用是把一些功能抽象出来,然后让其他类去继承它,这样就可以实现代码的重用。

概念

Mixin 故名思义 Mix in,也被称为混合类,不只是 python 中有,通常作为一种编程范式,在面对对象的语言中常见。Mixin 的作用是为一个类提供额外的功能,而不需要继承这个类。Mixin 的特点是可以被多个类继承,且不会影响到其他类的继承关系。

举例

[背景](# 背景) 中的例子定义一个简单的类:

example.py
class Displayer():
def display(self, message):
print(message)


class LoggerMixin():
def log(self, message, filename='logfile.txt'):
with open(filename, 'a') as fh:
fh.write(message)

def display(self, message):
super().display(message)
self.log(message)


class MySubClass(LoggerMixin, Displayer):
def log(self, message):
super().log(message, filename='subclasslog.txt')


subclass = MySubClass()
subclass.display("This string will be shown and logged in subclasslog.txt")

在上述例子中,我们定义了三个类,其中 Displayer 类只有一个 display 方法,LoggerMixin 类有两个方法,一个是 log 方法,一个是 display 方法,MySubClass 类继承了 LoggerMixinDisplayer 类,MySubClass 类重写了 LoggerMixin 类中的 log 方法,这样就实现了在 MySubClass 类中,display 方法既会显示到屏幕,又会写入到文件中。

逻辑很简单,但是仔细看,在第 6 行中,我们定义的 LoggerMixin 实际上没有继承任何父类,那何来的 super() 方法呢?

tip

super 方法是调用父类的方法。

在多继承的环境下,super() 方法有比较复杂的含义,它会按照 MRO(Method Resolution Order)继承链的顺序来查找父类,MRO 的顺序是在类定义时就确定的,不会随着子类的定义而改变。

触发过程

那么,在调用 MySubClass.display() 方法时,触发了以下行为

  1. 首先调用了 LoggerMixin.display 方法,因为在继承关系上,它最近
  2. 其次 LoggerMixin.display 调用 super().display(),上文说到,super() 会寻找当前类的 MRO,那么当前类是 MysubClass,在继承链上,过了 LoggerMixin 就是 Displayer,所以去找 Displayerdisplay 方法。
  3. 当然也要调用 LoggerMixinself.log 方法,那么这个 self 又是谁?,实际上也是 MySubClass 的实例,所以调用的是 MySubClass.log(),那为什么又可以输出到文件呢?因为 MySubClass.log() 调用了 super().log(),MRO 来寻找最近的父类,那就是 LoggerMixin.log() 咯!

讲清楚这一系列触发过程,基本就能理解 Mix-in 这个概念了

简单理解(省流)

在调用实例方法中,我们会现在当前类中找,没有的话,就去 MRO 中找,顺序是从左到右 ➡️。

所以 Mixin 类专注于多继承函数的调用问题,总是需要和其他类混合来加强功能!

用途

两个字:优雅!可以大大简化代码开发过程,减少耦合。

实际中的应用

在 Django 和 DRF 框架中,应用较多,比如有两个接口,一个接口 A 需要返回表格展示所需数据,一个接口 B 需要返回这些数据的文件下载流,在业务逻辑上,两个接口是一样的,那么就可以用混合类的方法,接口 B 继承一个混合类 C 和 A,其中混合类重写 get 方法,来返回文件流,并继承 A 的数据获取。

note

在 VSCode 中,如果混合类的类名后缀没有包含 Mixin,编辑器会给波浪线,但似乎并不影响代码运行。

Here's a simple footnote,1 and here's a longer one.bignote

Table Header 1Table Header 2Table Header 3
Division 1Division 2Division 3
Division 1Division 2Division 3
Division 1Division 2Division 3
Align LeftAlign CenterAlign Right
Division 1Division 2Division 3
Division 1Division 2Division 3
Division 1Division 2Division 3

  1. This is the first footnote.
  2. Here's one with multiple paragraphs and code. Indent paragraphs to include them in the footnote. {my code} Add as many paragraphs as you like.