轻松入门学python(四)python类的继承、添加与覆盖

Python 类的继承、添加与覆盖:从语法到设计思想的完整指南  
————————————————————  
(全文约 2000 字,示例基于 Python 3.11)

一、为什么要继承  
1. 代码复用:子类自动拥有父类的属性与方法,减少重复。  
2. 扩展与特化:在父类基础上增加新功能(添加),或改写已有实现(覆盖),使类型体系更符合领域模型。  
3. 多态:通过继承+方法覆盖,实现“一个接口,多种实现”,让高层代码只依赖父类接口即可操作所有子类对象。  

二、最简继承语法  

class Animal:               # 父类(基类)
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError

class Dog(Animal):          # 子类(派生类)
    def speak(self):        # 覆盖
        return f"{self.name}: 汪汪!"

Dog 自动获得 __init__,但 speak 被重写。

三、添加:为子类注入新成员  
1. 添加新属性  

class Cat(Animal):
    def __init__(self, name, lives=9):
        super().__init__(name)   # 先初始化父类
        self.lives = lives       # 再扩展自己的属性

2. 添加新方法  

    def purr(self):
        return f"{self.name} 正在打呼噜"

Cat 实例同时拥有 `speak`(继承)、`purr`(新增)、`lives`(新增)。

四、覆盖:同名即替换  
1. 完全替换:子类方法签名与父类一致,实现完全不同。  
2. 扩展实现:子类先调用父类逻辑,再追加。  

class Bird(Animal):
    def __init__(self, name, can_fly=True):
        super().__init__(name)
        self.can_fly = can_fly

    def speak(self):               # 覆盖
        return f"{self.name}: 啾啾!"

    def move(self, distance):      # 覆盖 + 扩展
        base = super().move(distance) if hasattr(super(), 'move') else ''
        if self.can_fly:
            return base + f" 飞行了 {distance} 米"
        return base + f" 跳跃了 {distance} 米"

要点:  
- 使用 `super()` 调用父类同名方法,避免“硬编码父类名”带来的维护成本。  
- 覆盖不限于实例方法,也适用于类方法、静态方法、属性。  

五、多重继承与 MRO  
Python 允许多继承,方法解析顺序由 C3 线性化算法决定,可通过 `Class.__mro__` 查看。  

class Flyable:
    def move(self):
        return "用翅膀飞"

class Swimmer:
    def move(self):
        return "用鳍游泳"

class Duck(Flyable, Swimmer):   # 菱形继承
    pass

print(Duck.__mro__)
# (, ,
#  , )

当 Duck 未覆盖 move 时,实际调用的是 Flyable.move。若想显式组合两种行为

class Duck(Flyable, Swimmer):
    def move(self):
        return Flyable.move(self) + ",也能" + Swimmer.move(self)

六、属性覆盖与描述符  
1. 数据属性覆盖  

class Parent:
    x = 10

class Child(Parent):
    x = 20     # 同名即遮蔽


2. 通过 `property` 覆盖 getter/setter  

class Celsius:
    def __init__(self, temp=0):
        self._temp = temp

    @property
    def temperature(self):
        return self._temp

class Fever(Celsius):
    @property
    def temperature(self):
        return super().temperature + 273.15   # 开尔文扩展

七、魔术方法的继承与扩展  

class MyList(list):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.access_count = 0

    def __getitem__(self, index):
        self.access_count += 1
        return super().__getitem__(index)


通过继承内置类型并重写魔术方法,可获得“原生行为 + 日志/缓存/权限”等增强功能。

八、super() 的完整语义  
1. 在单继承中,super() ≈ 父类。  
2. 在多继承中,super() 按 MRO 把调用委托给下一个类,常用于“协作式多重继承”。  

class Base:
    def method(self):
        print("Base")

class Left(Base):
    def method(self):
        super().method()
        print("Left")

class Right(Base):
    def method(self):
        super().method()
        print("Right")

class Bottom(Left, Right):
    def method(self):
        super().method()
        print("Bottom")

Bottom().method()
# Base
# Right
# Left
# Bottom


注意输出顺序与 MRO 一致:`Bottom → Left → Right → Base → object`。

九、抽象基类(ABC)与接口约束  
使用 `abc` 模块强制子类实现特定方法,防止“漏覆盖”。  

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        ...

class Circle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):        # 必须实现,否则实例化抛 TypeError
        return 3.1416 * self.r ** 2

十、组合优于继承?  
继承是“is-a”关系,组合是“has-a”关系。当子类需要复用父类实现但语义上并非“是一种”时,优先考虑组合:  

class Engine:
    def start(self): ...

class Car:
    def __init__(self):
        self.engine = Engine()   # 组合
    def start(self):
        self.engine.start()

十一、实战模式:Template Method  
在父类骨架中定义算法步骤,子类通过覆盖个别步骤实现变化。  

class Report:
    def generate(self):
        self.header()
        self.body()
        self.footer()

    def header(self):
        print("=== 报告头 ===")

    def body(self):
        raise NotImplementedError

    def footer(self):
        print("=== 报告脚 ===")

class PDFReport(Report):
    def body(self):
        print("以 PDF 格式输出正文")

class ExcelReport(Report):
    def body(self):
        print("以 Excel 格式输出正文")

十二、运行时动态修改:Monkey Patch 与继承的区别  
继承是“设计期”决定的类型层级;Monkey Patch 是在运行期为既有类或实例打补丁。  

def new_speak(self):
    return "打补丁的汪汪"

Dog.speak = new_speak    # 所有 Dog 实例生效


虽然能达到“覆盖”的效果,但破坏了封装,慎用。

十三、常见陷阱  
1. 忘记 super() 导致父类构造器未执行,属性缺失。  
2. 在 `__init__` 中调用可被覆盖的方法,子类尚未初始化完毕,易触发 AttributeError。  
3. 多继承层级过深,MRO 难以预测,调试困难。  
4. 过度使用继承导致“类爆炸”,可引入策略模式、装饰器模式等解耦。

十四、最佳实践小结  
- 使用继承表达清晰的“is-a”语义,避免仅为复用而继承。  
- 子类 `__init__` 必须 `super().__init__()`,且放在最前。  
- 覆盖方法时,先写 `super()` 保留父类行为,再差异化扩展。  
- 多继承场景优先使用 Mixin:只包含少量功能的小类,命名以 -able/-Mixin 结尾。  
- 对外暴露的 API 尽量基于抽象基类,内部实现可自由继承、替换。  
- 用组合或委托封装第三方库,避免直接继承其具体类,减少升级成本。

十五、结语  
继承、添加与覆盖构成了 Python 面向对象编程的“三板斧”。掌握它们不仅在于语法,更在于理解“契约式设计”与“开闭原则”:对扩展开放,对修改关闭。通过合理的继承层次、谨慎的方法覆盖以及必要的抽象约束,可以构建出既灵活又稳健的系统。

谢谢大家阅读!欢迎评论区斧正

你可能感兴趣的:(python,开发语言)