面向对象编程:类、继承与方法分派
前面我们一直把"数据"和"操作数据的函数"分开写:先定义
make_rat,再写add_rat。
但真实世界里,一个"学生"既有一堆属性(姓名、成绩),又有一堆行为(选课、算 GPA)。把它们拆在两处,代码会越来越散、越来越难找。
面向对象(OOP) 的解法很直白:把"数据"和"操作它的方法"打包进同一个类(class)。本篇从self这个最小齿轮讲起,一直讲到 Python 怎么决定"调的是哪个方法"(MRO)。
一、类与实例:蓝图与成品
是什么:
- 类(class) 是对象的"蓝图",描述一类事物长什么样、能干什么;
- 实例(instance) 是按蓝图造出的具体对象;
self是约定俗成的第一个参数,代表"当前这个实例自己"。
1 | class Dog: |
输出:
1 | 阿黄: 汪! |
__init__ 不是必须的,但最常见——它负责"出生初始化"。每次 Dog("阿黄"),Python 先造一个空实例,再把实例作为 self 传给 __init__。所以 self.name 实际上是在"这只具体的狗"身上挂了个名字。
坑:新手常忘记在方法里写 self. 前缀,写成裸变量 name = name,结果属性根本没存进实例,下次取就是 AttributeError。凡是"属于这个实例的状态",都要 self.xxx。
本质一句话:类=蓝图、实例=成品、self=当前实例;状态一律挂在 self 上才真正属于对象。
二、方法就是"绑了 self 的函数"
是什么:类里的函数叫方法(method)。调用 d.bark() 时,Python 自动把 d 作为 self 塞进第一个参数。这跟篇二"函数是对象"一脉相承——方法本质是绑在类上的函数。
1 | 调用 d.bark() 时 Python 在背后做的: |
坑:如果你用"函数式"写法直接拿类里的方法 f = Dog.bark,调用时得自己传 self:f(d)。大多数时候用 d.bark() 让 Python 帮你传,别自找麻烦。
本质一句话:方法 = 第一个参数是 self 的函数;obj.method() 就是 Class.method(obj) 的语法糖。
三、继承:复用与特化
是什么:继承(inheritance) 让子类自动获得父类的属性和方法,并可在子类里覆盖(override) 同名方法实现"特化"。
1 | class Animal: |
输出:
1 | 咪咪: 喵~ |
子类没写 __init__ 就用父类的;写了 speak 就覆盖父类版本。继承的价值不在"少写几行",而在"建立 is-a 的层次,统一接口"——你可以用 Animal 类型的变量装下猫和狗,统一调 speak()。
坑:继承链过深过宽会让系统变脆——改父类一个方法,所有子类行为都可能偷偷变。业界经验法则:优先组合(has-a)而非继承(is-a)。一只"会叫的机器狗"未必是 Animal 的子类,可能只是"装了个扬声器"。
本质一句话:继承 = 子类复用父类并覆盖特化;但别滥用,组合常比继承更稳。
四、方法分派与 MRO:调的到底是哪个
是什么:当 obj.method() 被调用,Python 沿 MRO(方法解析顺序,Method Resolution Order) 查找方法:先实例自己、再当前类、再按继承顺序往上,找到第一个就停。
1 | class A: |
输出:
1 | B |
坑(钻石继承):当继承出现"菱形"(D 同时继承 B 和 C,而 B、C 都继承 A),MRO 决定了 A 的方法只被解析一次、且顺序可预测。Python 用 C3 线性化算法保证这一点——简单记法是"按 __bases__ 顺序深度优先、去重、保序"。复杂继承树下,不画 MRO 就猜"调的是谁",基本必错。
1 | class A: pass |
本质一句话:方法查找走 MRO(实例→类→父类,第一个命中即停);多继承靠 C3 线性化保证顺序确定。
五、super():在子类里接住父类逻辑
是什么:覆盖方法时,常想"先跑父类的版本,再补点自己的"。super() 就是拿父类版本的钥匙,避免把父类名写死、也避免多继承时重复调用。
1 | class Animal: |
坑:在单继承里 super() 就是"父类";但在多继承里 super() 指向的是 MRO 上的"下一个类",未必是你直觉里的那个父类。所以多继承 + super() 必须配合对 MRO 的理解,否则初始化会漏掉某个基类。
小结
- 类是蓝图、实例是对象、
self指自己;状态一律挂self。 - 方法 = 带
self的函数;obj.m()等价于Class.m(obj)。 - 继承复用并覆盖特化;但优先组合、慎用深继承。
- 方法查找走 MRO(实例→类→父类,首个命中);多继承靠 C3 线性化定序;
super()接父类逻辑。

