在Python面向对象编程中,类属性与实例属性的查找顺序(MRO)是理解多继承行为的关键。很多开发者在单继承场景下游刃有余,但一遇到菱形继承或多继承,属性解析就会出问题。根本原因在于对属性查找规则和底层MRO算法的不熟悉。本文从基础概念到C3算法,结合代码示例,彻底讲清Python 3下的属性查找机制。
- class A:
- # 类属性:属于类,所有实例共享
- name = "类A的属性"
-
- def __init__(self):
- # 实例属性:属于当前实例,独立存储
- self.name = "实例的属性"
- obj = A()
- # 实例属性优先于类属性
- print(obj.name) # 输出:实例的属性
- # 类名直接访问类属性
- print(A.name) # 输出:类A的属性
复制代码
属性查找遵循“由下而上”规则:先查实例自身的属性空间,若无则查类的属性空间,再逐级向上查父类直至object。实例属性会覆盖同名类属性,类属性是兜底。
多继承让查找变得复杂。Python历史上经历过三种算法:
1. 深度优先搜索(Python 2.2前经典类):非菱形继承可用,但菱形继承会导致父类重写方法被跳过。
2. 广度优先搜索(Python 2经典类优化):解决了菱形继承问题,但破坏了“先继承的父类优先级更高”的规则。
3. C3算法(Python 2.3至今,Python 3统一采用):兼顾两类场景,顺序稳定可预测。
对开发者而言,无需深究C3算法公式,用内置的__mro__属性即可查看类的属性查找顺序。
- # 菱形继承:D → B、C → A
- class D:
- name = "D类"
- class B(D):
- pass
- class C(D):
- name = "C类"
- class A(B, C):
- pass
- print(A.__mro__)
- # 输出:(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
- # 顺序:A→B→C→D→object,C中重写的name优先于D
复制代码- # 普通多继承:D→B、E→C → A(B,C)
- class D:
- name = "D类"
- class E:
- name = "E类"
- class B(D):
- pass
- class C(E):
- pass
- class A(B, C):
- pass
- print(A.__mro__)
- # 输出:(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)
- # 顺序严格遵循继承优先级
复制代码
Python 3中所有类默认继承object(新式类),无需手动写class A(object):,这保证了C3算法统一生效。
总结三条核心规则:
- 单继承:实例属性优先,类属性兜底,逐级向上。
- 多继承:Python 3用C3算法,用__mro__查看顺序。
- 菱形继承:C3保证子类重写优先,父类兜底。
掌握这些,无论类属性、实例属性的定义,还是多继承下的属性冲突排查,都能轻松应对。 |