继承
继承是面向对象的核心机制之一。Python 的继承语法简洁,但底层行为与 C++ 等静态语言有显著差异:所有实例方法本质上都是虚方法,方法解析支持动态调整,且内置类型也可被用户扩展。
单继承语法
派生类定义将基类置于类名后的圆括号中:
class Base:
def __init__(self, value):
self.value = value
def show(self):
return f'Base: {self.value}'
class Derived(Base):
def double(self):
return self.value * 2
d = Derived(21)
print(d.show()) # Base: 21 —— 继承自 Base
print(d.double()) # 42 —— Derived 自有方法
基类名称必须能在定义派生类的作用域中被解析。当基类位于其他模块时,使用模块限定名:
# class Derived(some_module.Base):
# pass
派生类的实例化没有任何特殊之处。Derived() 创建空对象后,如果定义了 __init__,则调用它。若派生类未定义 __init__,解释器会自动沿继承链查找并使用第一个找到的基类 __init__。
方法重写
派生类可以定义与基类同名的方法来重写(override)基类实现。由于 Python 的方法在调用同一对象的其他方法时没有特殊权限,基类方法调用另一方法时,实际执行的可能是派生类中重写后的版本。
class Base:
def greet(self):
return f'Hello, {self.name()}'
def name(self):
return 'Base'
class Derived(Base):
def name(self): # 重写 name
return 'Derived'
print(Derived().greet()) # Hello, Derived
这与 C++ 中需要显式声明 virtual 不同——Python 中所有方法默认都是虚方法。设计基类时,必须意识到 self 引用的始终是实际实例所属的最低层派生类。
调用基类方法
重写方法通常需要扩展而非完全替换基类逻辑。最直接的方式是通过类名显式调用:
class Base:
def __init__(self, value):
self.value = value
def report(self):
return f'value={self.value}'
class Derived(Base):
def __init__(self, value, extra):
Base.__init__(self, value) # 显式调用基类构造
self.extra = extra
def report(self):
base = Base.report(self) # 显式调用基类方法
return f'{base}, extra={self.extra}'
d = Derived(10, 'bonus')
print(d.report()) # value=10, extra=bonus
这种方式直观,但存在局限:仅当基类可在全局作用域中以该名称访问时才有效;在多重继承场景中,无法正确处理菱形结构中的协同调用。
super() 函数
super() 返回一个代理对象,将方法调用委托给基类。在单继承中,它等价于通过基类名调用,但写法更健壮:
class Derived(Base):
def __init__(self, value, extra):
super().__init__(value) # 无需硬编码 Base
self.extra = extra
super() 的零参数形式(Python 3 新增)会自动推断当前类和实例。在方法内部,super() 等价于 super(CurrentClass, self)。
super() 的真正威力体现在多重继承中(详见多重继承章节),它能按照方法解析顺序(MRO)找到下一个应该被委托的类,而不仅仅是直接父类。
方法解析顺序(MRO)
每个类都有一个 __mro__ 属性,它是一个元组,按照属性查找的顺序列出该类及其所有基类。在单继承中,MRO 就是从子类到父类再到 object 的线性链条。
class A:
pass
class B(A):
pass
class C(B):
pass
print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
当访问实例属性时,Python 首先在实例的 __dict__ 中查找;若未找到,则沿 __mro__ 依次在每个类的 __dict__ 中查找,直到 object。
类还提供了 mro() 方法,返回与 __mro__ 相同的列表(但为列表类型)。子类可以通过重写 mro() 来自定义方法解析顺序,但这极为罕见且容易出错。
isinstance 与 issubclass
isinstance(obj, cls) 检查对象是否是指定类或其派生类的实例。它考虑继承关系,因此比 type(obj) is cls 更灵活:
class Animal:
pass
class Dog(Animal):
pass
d = Dog()
print(isinstance(d, Dog)) # True
print(isinstance(d, Animal)) # True —— 考虑继承
print(type(d) is Dog) # True
print(type(d) is Animal) # False —— type 不考虑继承
isinstance 的第二个参数可以是元组,用于检查多个类型中的任意一个:
print(isinstance(d, (Dog, int, str))) # True
issubclass(cls, base) 检查类继承关系:
print(issubclass(Dog, Animal)) # True
print(issubclass(bool, int)) # True —— bool 继承自 int
print(issubclass(float, int)) # False
print(issubclass(Dog, (Animal, int))) # True
注意 issubclass 要求第一个参数必须是类(或类型),传入实例会引发 TypeError。
继承内置类型
Python 允许用户类继承内置类型,这是与 C++ 和 Modula-3 的重要区别。继承 list、dict、str 等类型可以方便地扩展功能:
class TypedList(list):
"""只允许存储特定类型的元素"""
def __init__(self, type_, iterable=None):
self.type_ = type_
super().__init__()
if iterable:
for item in iterable:
self.append(item)
def append(self, item):
if not isinstance(item, self.type_):
raise TypeError(f'Expected {self.type_.__name__}, got {type(item).__name__}')
super().append(item)
nums = TypedList(int, [1, 2, 3])
nums.append(4)
print(nums) # [1, 2, 3, 4]
# nums.append('a') # TypeError: Expected int, got str
继承内置类型时需要注意:某些内置方法(如 list.__init__)可能不调用被重写的 append,导致类型检查被绕过。对于要求严格的场景,通常更推荐组合(composition)而非继承。