讲解

继承让一个类(子类)复用另一个类(父类/基类)的代码:class Dog(Animal): 括号里写父类名。子类自动拥有父类的所有属性和方法,可以添加新东西,也可以「重写」(override)父类的方法——子类定义同名方法,调用时优先用子类的。继承表达「is-a」关系:狗是一种动物,泰迪是一种狗。如果关系是「has-a」(汽车有发动机),该用的是组合(把发动机对象存为属性)而不是继承。

子类想在自己的 init 里先执行父类的初始化,用 super().init(参数)——super() 返回父类的代理对象,是调用父类方法的标准方式。判断对象和类的关系用两个内置函数:isinstance(dog, Animal) 为 True(子类对象也是父类的实例,这就是「里氏替换」的日常体现),type(dog) is Animal 则是严格类型相等;issubclass(Dog, Animal) 判断类之间的继承关系。

Python 支持多继承:class C(A, B),方法查找按 MRO(方法解析顺序,C3 线性化算法)进行,可以用 C.mro 查看。多继承功能强但复杂,工程上建议克制:绝大多数场景单继承加「组合」就够了;多继承的一个被广泛接受的用法是 Mixin——只提供单一功能、不单独实例化的小类(如 JsonSerializableMixin)。最后记住万物皆对象:所有类都隐式继承 object,object 提供了 streq 等基础方法(下一章的魔术方法就是重写它们)。

示例

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return f'{self.name} 发出声音'

    def introduce(self):
        return f'我是 {self.name},{self.speak()}'  # 调用的是子类重写的 speak


class Dog(Animal):
    def speak(self):  # 重写父类方法
        return f'{self.name} 汪汪叫'


class Cat(Animal):
    def speak(self):
        return f'{self.name} 喵喵叫'


animals = [Dog('旺财'), Cat('咪咪'), Animal('神秘生物')]
for a in animals:
    print(a.introduce())  # 多态:同一调用,各自表现


# super():子类初始化先走父类
class PoliceDog(Dog):
    def __init__(self, name, badge_no):
        super().__init__(name)  # 父类管 name
        self.badge_no = badge_no  # 子类加自己的

    def speak(self):
        return f'{super().speak()}(警犬证号:{self.badge_no})'


pd = PoliceDog('雷霆', 'K9-001')
print(pd.introduce())

# isinstance 与继承链
print('警犬是狗吗:', isinstance(pd, Dog))
print('警犬是动物吗:', isinstance(pd, Animal))
print('狗是动物的子类:', issubclass(Dog, Animal))


# 多继承与 MRO
class Swimmer:
    def move(self):
        return '游泳'


class Flyer:
    def move(self):
        return '飞行'


class Duck(Animal, Swimmer, Flyer):
    pass


print('MRO:', [c.__name__ for c in Duck.__mro__])
duck = Duck('唐老鸭')
print('Duck.move 用的是:', duck.move())  # Swimmer 排在 Flyer 前

常见坑

  • 重写 init 忘了 super().init():父类安的属性全没了,访问时报 AttributeError。子类 init 第一行通常就是 super() 调用。
  • isinstance 当 type 用:想严格判断「就是这个类、不含子类」用 type(x) is X;isinstance 会因子类通过而误判,反之需要多态时又误用 type。想清楚再选。
  • has-a 关系硬用继承:汽车继承发动机是概念错误——把发动机对象存进 self.engine 才是组合,组合优先于继承是面向对象的通则。
  • 多继承菱形问题不自知:两个父类有同名方法时谁生效由 MRO 决定,不看 mro 全凭运气。需要多继承时优先设计成 Mixin。

小结

子类继承父类、重写覆盖、super() 调用父类;isinstance 认继承链;组合优先于继承,多继承看 mro。下一章认识双下划线的魔法:魔术方法。