skip to content
Logo Lostman_Wang的小站

python面向对象-面向对象高级和应用

一、三大特性—继承补充

1、什么是 MRO?

MRO 的全称是 Method Resolution Order,即方法解析顺序。

当你在一个对象上调用一个方法(例如 obj.method()) 时,Python 需要沿着继承链向上搜索,以确定应该调用哪个类中的方法。这个搜索的顺序就是 MRO。

对于简单的单继承,这非常直观:从子类到父类,一路向上。

class A:
def method(self):
print("来自 A")
class B(A):
pass
class C(B):
pass
obj = C()
obj.method() # 输出:来自 A
# 搜索顺序:C -> B -> A -> object

核心问题:多重继承的歧义 当引入多重继承后,继承图不再是简单的“链”,而可能是一个复杂的“图”,甚至是一个“菱形”。这就产生了歧义:应该以何种顺序搜索父类?

class A:
def method(self):
print("来自 A")
class B(A):
def method(self):
print("来自 B")
class C(A):
def method(self):
print("来自 C")
class D(B, C):
pass
obj = D()
obj.method() # 应该输出什么?是 B 的还是 C 的?

在这个经典的“菱形继承”问题中,搜索顺序可以是:

  1. D -> B -> A -> C -> A(深度优先,但重复访问 A 是糟糕的)
  2. D -> B -> C -> A(广度优先)
  3. 或者其他顺序…

Python 必须有一个一致、可靠且可预测的规则来决定这个顺序。这就是 MRO 算法要解决的问题。

2、C3 算法详解

C3 算法的核心是为一个类生成一个线性化(MRO 列表),这个线性化需要满足以下两个关键约束:

  1. 继承顺序规则 (局部优先顺序)

    子类继承列表中父类的顺序被保留。

  2. 单调性规则 (子类优先于父类)

    子类的 MRO 必须包含所有父类的 MRO,并保持其内部顺序。

C3 算法的步骤与公式

C3 算法的操作可以描述为一个递归的合并过程。对于一个类 C,其继承列表为 [B1, B2, ..., BN]C 的 MRO 计算如下:

L[C] = [C] + merge(L[B1], L[B2], ..., L[BN], [B1, B2, ..., BN])

这里的 L[Cls] 代表类 Cls 的 MRO 列表,merge 是一个特殊的合并函数。

merge 操作规则

merge 函数接收多个列表(线性化)作为参数,并按以下规则合并它们:

检查第一个列表的第一个元素

如果这个元素没有出现在任何其他列表的非首位,那么它就是一个“好头”,可以将其从所有列表中移除,并添加到输出结果中。

如果这个元素出现在其他列表的非首位,则跳过它,去检查下一个列表的第一个元素。

重复这个过程,直到所有列表都被耗尽。

如果找不到符合规则的“好头”,则算法无法构建一个一致的线性化,Python 会抛出 TypeError(这意味着类的继承 hierarchy 本身就有问题)。

二、内置函数补充

callable

用来 判断某个对象“能不能在后面加一对括号进行调用”。能就 True,不能就 False。

1、签名与返回值
callable(obj) -> bool

返回 True:obj 可以被调用(不会保证调用一定成功,但语法层面合法)。

返回 False:obj 不能加 (),强行写 obj() 会抛 TypeError: 'xxx' object is not callable

2、常见返回 True 的对象

用户定义的函数(def/lambda

内置函数/方法(lenstr.upper

实现了 __call__ 的 类实例(下面示例)

类对象本身(调用类就是创建实例)

生成器函数、协程函数、偏函数 (functools.partial) 等

3、常见返回 False 的对象

普通数值、listdictstrtupleset

未实现 __call__ 的自定义类实例

None

4、自定义可调用对象

只要给类加上 __call__ 方法,实例就变成“像函数一样的东西”:

class Adder:
def __init__(self, offset):
self.offset = offset
def __call__(self, x):
return x + self.offset
add5 = Adder(5)
print(callable(add5)) # True
print(add5(10)) # 15
5、与 hasattr(obj, '__call__') 的区别

callable 在 C 层实现,速度快,且对 旧式类、扩展类型 兼容性更好。

hasattr(obj, '__call__') 只检查属性字典,某些内置类型(如 CPython 的 builtin_function_or_method)没有暴露 __call__,但依然是 callable

官方推荐:用 callable,别自己判断 __call__

6、典型用途

接口校验:先 assert callable(cb),再 cb()

反射/序列化框架:区分字段是普通数据还是回调函数。

装饰器内部:判断传入的是类还是函数,从而走不同分支。

super