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 的?在这个经典的“菱形继承”问题中,搜索顺序可以是:
D -> B -> A -> C -> A(深度优先,但重复访问 A 是糟糕的)D -> B -> C -> A(广度优先)- 或者其他顺序…
Python 必须有一个一致、可靠且可预测的规则来决定这个顺序。这就是 MRO 算法要解决的问题。
2、C3 算法详解
C3 算法的核心是为一个类生成一个线性化(MRO 列表),这个线性化需要满足以下两个关键约束:
-
继承顺序规则 (局部优先顺序)
子类继承列表中父类的顺序被保留。
-
单调性规则 (子类优先于父类)
子类的 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)
内置函数/方法(len、str.upper)
实现了 __call__ 的 类实例(下面示例)
类对象本身(调用类就是创建实例)
生成器函数、协程函数、偏函数 (functools.partial) 等
3、常见返回 False 的对象
普通数值、list、dict、str、tuple、set 等
未实现 __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)) # Trueprint(add5(10)) # 155、与 hasattr(obj, '__call__') 的区别
callable 在 C 层实现,速度快,且对 旧式类、扩展类型 兼容性更好。
hasattr(obj, '__call__') 只检查属性字典,某些内置类型(如 CPython 的 builtin_function_or_method)没有暴露 __call__,但依然是 callable。
官方推荐:用 callable,别自己判断 __call__。
6、典型用途
接口校验:先 assert callable(cb),再 cb()。
反射/序列化框架:区分字段是普通数据还是回调函数。
装饰器内部:判断传入的是类还是函数,从而走不同分支。