站内链接:

Defined

MRO: method resolution order, 方法或类解析顺序,用于确定类中方法, 属性的查找规则, 查找顺序.
LEGB: 作用域的查找逻辑, 其查找链条为Local-->Enclosing function locals-->Globls-->Built-in, 关于作用域, 命名空间, 闭包等相关知识点见站内连接作用域和命名空间文章说明.

为了下面素有章节的说明更加清晰化, 我们在这里先预定设置一个 python 继承的实例(1.1 例), 这是一个多继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
class A:
def show(self):
print('A')

class B(A):
pass

class C(A):
def show(self):
print('C')

class D(B, C):
pass

Rule

human 规则

  1. 经典类
  • 从左向右(多继承类中从左到右),深度优先遍历
  • 遍历中出现重复的类函数,保留首个
  • 上述实例的打印顺序: D, B, A, C, 所以D().show()的输出为: A
  1. 新式类(python2 中, A 继承 object)
  • 从左向右(多继承类中从左到右),深度优先遍历
  • 遍历中出现重复的类或函数,保留最后一个
  • D, B, A, object, C, A, object, 重复类仅仅保留最后一个, 故最后顺序: D, B, C, A, object
  • 上述实例的打印, D().show()输出为: C
  • 新式类可通过A.__mro__查看 mro 值信息

但是该逻辑在复杂的继承关系中会出现一些问题, 例如(2.1 例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A:
def show(self):
print('A')

class B(A):
pass

class C(A):
def show(self):
print('C')

class X(B, C):
pass

class Y(C, B):
pass

class D(X, Y):
pass

对于 X 来说, 其搜索顺序: B, A, object, C, 对于 Y 来说明, 其搜索顺序为: C, A, object, B, 此时会发现 X, Y 两者的搜搜顺序是相反的, 从而产生问题, 另外还违反了继承关系的单调性原则.

A MRO is monotonic when the following is true: if C1 precedes C2 in the linearization of C, then C1 precedes C2 in the linearization of any subclass of C. Otherwise, the innocuous operation of deriving a new class could change the resolution order of methods, potentially introducing very subtle bugs.

单调性原则: 子类不能改变基类的方法搜索顺序.

  1. C3 方法(python2.3 之后)
  • 继承新式类的查找逻辑
  • 禁止二义性的继承代码, 例如 2.1 例中如果出现class C(A, B)则会抛出异常
1
2
TypeError: Cannot create a consistent method resolution
order (MRO) for bases B, C

数学推导规则

  1. 将类 C 的线性化称呼为 MRO, 记为L[C] == [C1, C2, ..., Cn],其中 C1 就是 当前最底部的类,称为头,其余称为尾.
  2. 假定 C 类的继承关系为:
1
2
class C(B1, B2, ..., Bn):
pass

B1,B2, ..., Bn为倒数第二层级

根据推导得出:

1
2
a) L[object] = [object]
b) L[C] = [C] + merge(L[B1], L[B2], ..., L[Bn] , B1, B2, ..., Bn])

其中 merge 语法:

1
2
3
4
1. 检查首个元素的头部,记为 H,即为深度优先遍历最早的一个元素 H
2. 判断 H 是否在 L[B2], ..., L[Bn]中的尾部出现,即判断 H 是否在下一个层级
如果没有则取出 H 并删除其他元素中的头部 H 元素,并返回步骤-1
否则取下一个元素 L[B3]的首部记为 H, 重复当前步骤-2直到列表为空或者无法找出合法的元素(继承关系有问题)

推导公式:

1
2
3
4
5
6
7
8
9
L[A] = [A] + merge(L[X], L[Y], [X], [Y])
L[X] = [X, object]
L[Y] = [Y, object]
则:
L[A] = [A] + merge([X, object], [Y, object], [X], [Y])
= [A, X] + merge([object], [Y, object], [Y])
= [A, X] + merge([object], [Y, object], [Y]), H变为下一个 Y
= [A, X, Y] + merge([object], [object])
= [A, X, Y, object] 最终的线性化结果

对于如下的继承关系:
inherit

其完整的推导公式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
L[object] = [object]
L[D] = [D, object]
L[E] = [E, object]
L[F] = [F, object]
L[B] = [B, D, E, object]
L[C] = [C, D, F, object]
L[A] = [A] + merge(L[B], L[C], [B], [C])
= [A] + merge([B, D, E, object], [C, D, F, object], [B], [C])
= [A, B] + merge([D, E, object], [C, D, F, object], [C])
= [A, B, C] + merge([D, E, object], [D, F, object])
= [A, B, C, D] + merge([E, object], [F, object])
= [A, B, C, D, E] + merge([object], [F, object])
= [A, B, C, D, E, F] + merge([object], [object])
= [A, B, C, D, E, F, object]

例子

Mixin

mixin: 混入, 一种编程模式的用法, 每一个 mixin 都是某一个功能单元的类, 每一个 mixin 可以插入到任意子类继承链中, 多个 mixin 可以组合到一个子类的继承中, 其就是一个个模块化的单元插件, 根据程序自由组合.
使用场景: 一个守护轮询进程不断的消费 MQ 中的消息, 关于消息的解析, 消息的基本格式化都可以在回调基类中按照通用格式完成,
但是这并不能适用于所有的数据, 如果这些数据还有非常多的特征, 比如类型, 颜色, 大小等等一大堆不同的特性, 那么对于不同的特征肯定需要不同的函数来解析. 按照正常逻辑进行编写会导致各个数据类型的子类出现重复代码, 子类非常冗余, 那么此时 mixxin 就能非常方便的处理该使用场景.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class BaseData:
def show(self):
print('Base')

class ColorMixin:
def parse_color(self):
print('parse color')

def show_color(self):
print('show color')

class TypeMixin:
def is_valid_type(self):
return True

class SizeMixin:
def is_overload(self):
return False

def show_size(self):
print('size')

class ColorTypeChild(BaseData, ColorMixin, TypeMixin):
pass

class SizeChild(BaseData, SizeMixin):
pass

通过这种即插即用自由实现需要的功能

拆分实现逻辑

在某次应用场景中, 底层基类实现逻辑代码非常多, 并且代码分为两种操作类型: 数据库对象操作, 字符串序列化操作, 并且后者需要同其他微服务一起进行数据的请求和数据的解析操作, 这是为了以后代码迁移更加方便, 故而将这两部分代码拆分开来, 其整体逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class BaseHandler:
def log_info(self):
print('日志记录工作')

def serializer(self):
print('数据库对象序列化工作')

def DatabaseHandler(BaseHandler):
pass

class BaseMixin:
def waitting(self):
time.sleep(5)

def logging_after_request(self):
pass

def log_info(self):
pass

class OracleMixin(BaseMixin):
pass

class MysqlMixin(BaseMixin):
pass

def OracleDatabaseHandler(DatabaseHander, OracleMixin):
pass

def MysqlDatabaseHandler(DatabaseHander, MysqlMixin):
pass

通过上述的类继承关系, 将一个非常大的OracleDatabaseHandler给强行分开, 并且实现了处理类型的模块化, 只不过这里不应该叫做 mixin, 因为 mixin 中的函数一般都是独立的, 逻辑自洽的, 但是这里 BaseMixin, OracleMixin 会用到 BaseHandler 中创建的很多数据, 此时可能会有疑惑, 这里仅仅介绍另外一种多继承可能的用法.
另外, 此时对于OracleDatabaseHandler, 其查找顺序: ODH, DH, BH, OM, BM, 所以OracleDatabaseHandler().log_info()实际上调用的是基类的实现代码, 当然, 这也可以当成是多继承乱用的反例.

Namespace and Field

namespace–”命名–对象的映射”,在 Python 中使用 dictionary 来实现
field–直接能够访问到的”namespace”

属性名查找顺序

“属性”—任何”.”后面的名称

类:实例属性–>类属性–>父类属性(MRO)

模块:模块作用域–>导入包作用域

namespace

namespace 们在”不同的时刻”创建,存在着不同的声明周期.

builtins: 内置名称的命名空间在 Python interpreter 启动时创建,模块名为 builtins

module: 模块的全局命名空间在模块定义被导入读取时创建,一般伴随着 Python interpreter 的整个声明周期,被顶级解释器调用的属性在** main**模块中

functions: 函数的命名空间在函数创建时定义,返回或者抛出异常时销毁

field

field–仅仅是一个结构上的区域范围,它在结构上决定作用区域.

permissions: 对于某一个 namespace,至少三个嵌套的 field 可以直接访问:

  • 最内层的作用域,locals
  • 全局变量 globals(函数名, 全局变量)
  • 最外层, builtins 名称

update

  • 赋值并没有拷贝数据,仅仅绑定名字到对象上
  • del X 仅仅是将变量 X 从当前作用域移除绑定
  • import 就是将模块和函数绑定到当前作用域

nonlocal 和 global

global: 指示全局作用域中的变量可以绑定到当前作用域(引用),如果全局命名空间在一开始不存在变量 X, 则赋值之后就会存在.
nonlocal: 指示在一个闭合的作用域中的变量可以在此绑定