)
前言这篇文章回答两个问题继承有什么用以及语法怎么写。先说明一点标题把作用和语法并列其实两者是分不开的——语法很简单一个括号、一个类名难点在于判断什么时候该用。很多人学会了class Child(Parent):之后见什么都想继承结果把无关的类硬串成一条继承链越改越乱。继承能带来三样东西复用父类写好的方法子类直接能用扩展子类可以在父类基础上加东西、改行为统一接口一组类拥有同名方法调用方可以用同一种方式对待它们。三样里前两样用组合在一个类里持有另一个类的实例往往也能做到真正只有继承才提供的是第三样。所以判断标准可以概括成一句如果两种东西之间是是一种is-a关系就用继承如果是有一个has-a关系就用组合。一、语法速查继承的语法就是类名后面加一对括号括号里写基类# 适用于 Python 3.8class Parent:def hello(self):return parentclass Child(Parent):passprint(Child().hello()) # parent几个要点括号里不写基类等价于继承object新式类这是 Python 3 的唯一形式括号里可以写多个基类多重继承用逗号分隔例如class C(A, B):子类在括号里写父类时父类必须已经定义在模块自上而下执行时。Python 2 的旧写法为什么不能用了老教程里常见class Foo(object):这种写法看多了会以为括号里的object是必需的。其实这是Python 2 的历史包袱在 Python 2 里如果写成class Foo:而不显式继承object得到的是旧式类old-style class它的属性查找规则、super()行为都和新式类new-style class不同——这正是当年到处写class Foo(object):的原因。Python 2.7 已于2020 年 1 月 1 日停止维护。进入 Python 3 后所有类都是新式类class Foo:与class Foo(object):完全等价括号里的object已经多余。# 适用于 Python 3.8下方一并对比 Python 2 的旧式类写法# Python 2 的写法旧式类不要再用class Old: # 没有显式继承 object → 旧式类passclass New(object): # 新式类pass# Python 3 的写法所有类都是新式类两种写法等价class Either:passclass AlsoFine(object): # 能跑但 object 是多余的pass同理Python 2 里的print x是语句不是函数3 里必须写print(x)dict.iteritems()/iterkeys()/itervalues()已被移除3 里用.items()/.keys()/.values()返回的是视图xrange也已移除3 里直接range本身就是惰性的。写新代码一律以 Python 3 为准。下面这张表把常见语法点集中列出语法含义class Child(Parent):单继承class C(A, B):多重继承MRO 见C.__mro__Child.__bases__直接基类的元组Child.__mro__方法解析顺序含自身与objectissubclass(Child, Parent)检查继承关系isinstance(obj, Parent)检查实例关系子类实例也算super().method(...)调用 MRO 中的下一个实现二、继承的三种用法用法一纯复用子类什么都不改直接拿父类的行为# 适用于 Python 3.8class BaseRepo:def __init__(self, conn):self.conn conndef query(self, sql):return self.conn.execute(sql)class UserRepo(BaseRepo):passrepo UserRepo(conn)repo.query(select 1)用法二扩展与覆盖子类改一部分、留一部分。super()是这里的核心# 适用于 Python 3.8class BaseRepo:table basedef all_rows(self):return fselect * from {self.table}class UserRepo(BaseRepo):table users # 覆盖类属性class AuditRepo(BaseRepo):table auditdef all_rows(self):return super().all_rows() limit 100 # 扩展print(UserRepo().all_rows()) # select * from usersprint(AuditRepo().all_rows()) # select * from audit limit 100UserRepo只是把类属性table换掉父类方法里引用的是self.table于是自然读到子类的值。这也说明父类方法里该用self.xxx而非硬编码具体值子类才有得改。AuditRepo则先用super().all_rows()拿到父类结果再追加实现了保留 扩展。用法三统一接口这是继承不可替代的价值# 适用于 Python 3.8class BaseRepo:table basedef all_rows(self):return fselect * from {self.table}class UserRepo(BaseRepo):table usersclass OrderRepo(BaseRepo):table ordersdef dump(repos):for r in repos:print(r.all_rows())dump([UserRepo(), OrderRepo()])# select * from users# select * from ordersdump不知道UserRepo和OrderRepo是什么只要求它们能响应all_rows()。三、属性查找与 super() 的正确定位继承的关键机制是属性查找顺序是先实例、再沿 MRO 找类。super()不是父类的同义词而是MRO 里的下一个类。单继承时两者恰好一致多重继承时就不一样了。# 适用于 Python 3.8class A:def who(self):return [A]class B(A):def who(self):return [B] super().who()class C(A):def who(self):return [C] super().who()class D(B, C):def who(self):return [D] super().who()print(C().who()) # [C, A]print(D().who()) # [D, B, C, A]print([c.__name__ for c in D.__mro__])# [D, B, C, A, object]D().who()的链条是D → B → C → A说明B里的super()跳到了C而不是跳到它的直接基类A。这就是协作式继承的机制。需要留意零参数super()只能在类体内部的普通方法里用在嵌套函数包括生成器表达式里它取不到正确的实例。四、继承还是组合决策清单出现下面任一情况优先用组合两个类之间说不出X 是一种 Y这句话子类只用到父类的一两个方法其余全是负担继承层次已经超过两三层改一处要担心牵连一圈子类覆盖了大量父类方法等于把父类推翻了。组合的写法是在__init__里持有一个实例并把需要的能力转发出去# 适用于 Python 3.8class Logger:def log(self, msg):return f[log] {msg}class Service:def __init__(self, logger):self.logger logger # has-a持有一个 Loggerdef run(self):return self.logger.log(running)print(Service(Logger()).run()) # [log] runningService不是一种Logger所以不该继承它——这个判断足够应付绝大多数情况。常见坑点1. 用继承表达有一个关系❌class Car(Engine):—— 车不是引擎只是有引擎。✅class Car: def __init__(self, engine): self.engine engine。2. 子类__init__忘了调super().__init__()❌ 父类设置的属性一个都没有访问时报AttributeError。✅ 在子类__init__开头先super().__init__(...)。3. 用type(x) Parent判断类型❌ 子类实例被判为False。✅ 用isinstance(x, Parent)。4. 覆盖方法时改了签名❌ 父类def save(self, path)子类def save(self)统一调用点TypeError。✅ 保持兼容签名新参数给默认值。5. 父类方法里硬编码了具体值子类改不动❌ 父类return select * from users子类想换表名只能整个重写方法。✅ 写成return fselect * from {self.table}子类只改类属性即可。6. 以为super()就是父类❌ 在多重继承里断言super()一定指向直接基类。✅ 它是 MRO 中的下一个类用类.__mro__实际确认为准。7. 在嵌套函数里用零参数super()❌ 在方法内的嵌套函数或生成器表达式里写super().foo()实例取不到。✅ 在方法里先把super()的结果存进变量再在嵌套函数中使用。8. 层级过深❌ 父 → 子 → 孙 → 曾孙改中间一层影响一大片。✅ 控制在两三层内更复杂的复用需求改用组合或 Mixin。总结问题答案继承怎么写class Child(Parent):多个基类用逗号分隔继承的作用复用、扩展、统一接口最后一项不可替代super()是什么MRO 中的下一个类不是父类该用继承吗能说出X 是一种 Y就用否则用组合怎么判断对不对打印__mro__看一眼够直观继承的语法一分钟就能学会难的是克制。真正需要继承的场景是一组类需要被同一种方式对待如果只是想复用几行代码组合往往更清爽。写下一个class Child(Parent):之前先在心里把X 是一种 Y这句话说出来能说通再动手。