欢迎光临
我们一直在努力

Python.三.(三)--3. 面向对象三大特征

三、 函数、类与面向对象编程 (OOP)

  • 标题:
  • 函数参数与高阶函数
  • 装饰器与闭包
  • 面向对象三大特征
  • 类、对象与魔术方法
  • 异常处理


介绍一下Python中的封装(Encapsulation)思想

封装 (Encapsulation) 在 Python 中的概念

封装是面向对象编程(OOP)的四大基本原则之一,其他三个是继承(Inheritance)、多态(Polymorphism)和抽象(Abstraction)。封装的核心思想是将对象的数据(属性)和行为(方法)打包在一起,并限制外界对它们的直接访问。通过封装,开发人员可以控制哪些数据可以从外部访问,哪些只能在类的内部使用。

Python 虽然不像一些其他面向对象的编程语言(如 Java、C++)那样严格地限制数据的访问,但它依然支持通过命名约定和访问控制来实现封装的概念。

封装的主要思想

封装主要涉及以下几个方面:

  • 隐藏内部实现:对象的内部状态对外界不可见,外界只能通过公开的接口(即方法)访问或修改对象的状态。
  • 保护对象的完整性:通过封装,类的设计者可以控制外部如何访问或修改内部数据,避免外部对内部数据进行非法的操作,确保对象的一致性和完整性。
  • 提供安全的访问接口:通过定义类的公有方法(public methods),外部可以在不直接操作内部数据的情况下,安全地对对象进行操作。
  • Python 中的封装机制

    在 Python 中,封装的实现主要依赖命名约定和访问控制,Python 没有像某些编程语言那样提供明确的访问权限控制符(如 Java 的 public、private、protected),但它有一些约定俗成的规则来实现封装。

    1. 公有成员 (Public Members)

    在 Python 中,默认情况下,类的所有属性和方法都是公有的(public)。这意味着外部可以直接访问或修改这些属性和方法。例如:

    class MyClass:
    def __init__(self, name):
    self.name = name # 公有属性

    def greet(self): # 公有方法
    return f"Hello, {self.name}"

    # 使用
    obj = MyClass("Alice")
    print(obj.name) # 直接访问公有属性
    print(obj.greet()) # 调用公有方法

    在这个例子中,name 属性和 greet() 方法都是公有的,外部可以直接访问它们。

    2. 私有成员 (Private Members)

    在 Python 中,使用双下划线 (__) 开头的属性或方法被认为是私有的,不能被类外部直接访问。这是通过名称重整(name mangling)实现的,Python 会在属性名前加上类名来避免外部访问它们。

    class MyClass:
    def __init__(self, name):
    self.__name = name # 私有属性

    def __private_method(self): # 私有方法
    return f"Hello, {self.__name}"

    def public_method(self):
    return self.__private_method() # 公有方法调用私有方法

    # 使用
    obj = MyClass("Alice")
    # print(obj.__name) # 会抛出 AttributeError
    # print(obj.__private_method()) # 会抛出 AttributeError
    print(obj.public_method()) # 可以通过公有方法间接访问私有方法

    在这个例子中,__name 属性和 __private_method() 方法是私有的,外部无法直接访问它们。如果尝试访问,会报 AttributeError 错误。但是,可以通过类内部的公有方法来访问私有成员。

    注意:虽然双下划线的属性和方法是"私有"的,但实际上 Python 只是对它们的名称进行了重整。你可以通过 _ClassName__attribute 的方式来访问它们,Python 并没有完全禁止访问。这种设计更多的是一种"约定"而不是强制的隐藏。

    # 通过名称重整访问私有属性
    print(obj._MyClass__name) # 通过 name mangling 访问私有属性

    3. 受保护成员 (Protected Members)

    在 Python 中,使用单下划线 (_) 开头的属性或方法被认为是受保护的,这是一个弱封装的约定。受保护的成员不建议在类外部直接访问,但并没有强制限制,可以通过子类继承和扩展时访问。

    class MyClass:
    def __init__(self, name):
    self._name = name # 受保护属性

    def _protected_method(self): # 受保护方法
    return f"Hello, {self._name}"

    # 使用
    obj = MyClass("Alice")
    print(obj._name) # 可以访问受保护属性,但不建议
    print(obj._protected_method()) # 可以访问受保护方法,但不建议

    受保护的成员可以在类外部访问,但一般在设计时,约定不应该直接访问这些成员,通常用于类内部或子类中。

    4. 公有方法与私有属性的结合使用

    一个常见的封装模式是将类的属性设置为私有,然后通过公有的方法(通常称为getter和setter方法)来控制外界如何访问或修改这些属性。这种方法允许对属性的访问进行更精细的控制,避免不当的操作。

    class MyClass:
    def __init__(self, name):
    self.__name = name # 私有属性

    def get_name(self): # getter 方法
    return self.__name

    def set_name(self, new_name): # setter 方法
    if isinstance(new_name, str):
    self.__name = new_name
    else:
    raise ValueError("Name must be a string")

    # 使用
    obj = MyClass("Alice")
    print(obj.get_name()) # 通过 getter 访问私有属性
    obj.set_name("Bob") # 通过 setter 修改私有属性
    print(obj.get_name())

    通过这种设计,程序员可以确保只有经过验证的数据才能修改属性。比如在 set_name 方法中,我们检查输入是否为字符串,如果不是,则抛出异常。这种方式有效地保护了类的内部状态。

    5. 属性装饰器 (@property) 的使用

    Python 提供了 @property 装饰器来简化 getter 和 setter 方法的定义,允许我们像访问普通属性一样调用方法。这是一种更 Pythonic 的封装方式。

    class MyClass:
    def __init__(self, name):
    self.__name = name # 私有属性

    @property
    def name(self): # getter 方法
    return self.__name

    @name.setter
    def name(self, new_name): # setter 方法
    if isinstance(new_name, str):
    self.__name = new_name
    else:
    raise ValueError("Name must be a string")

    # 使用
    obj = MyClass("Alice")
    print(obj.name) # 通过属性访问
    obj.name = "Bob" # 修改属性
    print(obj.name)

    @property 允许你将方法包装成属性的形式,从而使类的使用更加直观,同时保持了封装性。

    • @property:将方法转化为属性,用于读取。
    • @name.setter:为属性定义赋值逻辑,用于写入。

    🆕 5.1 @property.deleter 与只读属性

    class Config:
    def __init__(self, db_url):
    self._db_url = db_url

    @property
    def db_url(self):
    """只读属性:外部可读取但不能直接修改"""
    return self._db_url

    @db_url.deleter
    def db_url(self):
    """删除属性时的清理逻辑"""
    print("Cleaning up database connection…")
    del self._db_url

    config = Config("postgresql://localhost:5432/mydb")
    print(config.db_url) # ✅ 可读取
    # config.db_url = "xxx" # ❌ 没有 setter → AttributeError
    del config.db_url # ✅ 触发 deleter

    🆕 5.2 只读计算属性模式

    import time

    class Circle:
    def __init__(self, radius):
    self.radius = radius # 半径是可变的

    @property
    def area(self):
    """计算属性:每次读取时实时计算,不可赋值"""
    return 3.14159 * self.radius ** 2

    @property
    def diameter(self):
    return self.radius * 2

    c = Circle(5)
    print(c.area) # 78.53975
    c.radius = 10
    print(c.area) # 314.159(自动更新)
    # c.area = 100 # ❌ 只读属性无法赋值

    🆕 6. 描述符协议 (Descriptor Protocol) —— @property 的底层原理

    @property 本质上是一个数据描述符 (Data Descriptor)。理解描述符协议是掌握 Python 封装高级用法的关键。

    描述符协议:一个类如果实现了以下任意方法,就是一个描述符:

    • __get__(self, obj, objtype=None) → 用于属性访问
    • __set__(self, obj, value) → 用于属性赋值
    • __delete__(self, obj) → 用于属性删除 (del)

    # 手写一个非负数校验描述符(等价于带校验的 property)
    class NonNegative:
    """确保属性的值永远 >= 0"""
    def __init__(self, default=0):
    self.default = default
    self.private_name = None # 将在 __set_name__ 中设置

    def __set_name__(self, owner, name):
    # Python 3.6+ 自动调用:告诉我们被赋给了哪个属性名
    self.private_name = f'_{name}'

    def __get__(self, obj, objtype=None):
    if obj is None:
    return self
    return getattr(obj, self.private_name, self.default)

    def __set__(self, obj, value):
    if value < 0:
    raise ValueError(f"{self.private_name} must be >= 0, got {value}")
    setattr(obj, self.private_name, value)

    class Product:
    price = NonNegative(0) # 使用描述符
    stock = NonNegative(0) # 复用同一个描述符类

    def __init__(self, price, stock):
    self.price = price # 触发 NonNegative.__set__
    self.stock = stock

    p = Product(99.9, 50)
    print(p.price) # 99.9 → 触发 NonNegative.__get__
    # p.price = -1 # ❌ ValueError: _price must be >= 0

    关键洞察:

    特性@property自定义描述符
    复用性 每个属性单独定义 一个类可用于多个属性
    适用场景 单一属性的特殊逻辑 跨多个属性的通用校验/转换逻辑
    底层关系 @property 就是一个描述符工厂 描述符是更底层的原语

    PyTorch 的 nn.Parameter、Django ORM 的 Field 都是描述符协议的实际应用。

    🆕 7. __slots__ —— 内存优化与属性封禁

    __slots__ 是 Python 提供的一个类级别优化选项,同时具有封装增强的作用。

    class WithoutSlots:
    def __init__(self, x, y):
    self.x = x
    self.y = y

    class WithSlots:
    __slots__ = ('x', 'y') # 声明允许的属性名

    def __init__(self, x, y):
    self.x = x
    self.y = y

    # —- 功能差异 —-
    a = WithoutSlots(1, 2)
    a.z = 999 # ✅ 可以随意添加新属性

    b = WithSlots(1, 2)
    b.z = 999 # ❌ AttributeError: 'WithSlots' object has no attribute 'z'

    # —- 内存差异 —-
    import sys
    print(sys.getsizeof(WithoutSlots(1, 2))) # 通常 56 bytes (+ __dict__)
    print(sys.getsizeof(WithSlots(1, 2))) # 通常 32 bytes (无 __dict__)

    __slots__ 的封装意义:

  • 防止拼写错误:obj.nmae = 5 不会静默创建错误属性,而是立即报错
  • 节省内存:省去 __dict__ 和 __weakref__,适合创建大量小对象的场景
  • 加速属性访问: slots 使用 descriptor 而非 dict 查找,略快
  • ⚠️ 继承注意事项:

    • 子类不会自动继承父类的 __slots__,除非子类也定义了 __slots__
    • 如果父类没有定义 __slots__,子类定义了 __slots__ 则无效(因为 __dict__ 已存在)
    • 正确做法:父类和子类都定义 __slots__

    class Base:
    __slots__ = ('x',)

    class Derived(Base):
    __slots__ = ('y',) # 子类额外声明的 slot

    d = Derived()
    d.x = 1 # ✅ 来自 Base
    d.y = 2 # ✅ 来自 Derived
    # d.z = 3 # ❌ AttributeError

    🆕 8. 模块级封装

    Python 的封装不止于类层面,模块(module) 也有自己的封装机制:

    # ===== my_module.py =====

    __all__ = ['public_func', 'PUBLIC_CONST'] # 显式导出 API

    PUBLIC_CONST = 42

    def public_func():
    return "I'm part of the public API"

    def _internal_helper():
    """单下划线:约定为内部实现,from X import * 不会被导入"""
    return "Implementation detail"

    __secret_var = "double underscore: 更强的私有暗示"

    # ===== 使用方 =====
    from my_module import *
    print(public_func()) # ✅ 在 __all__ 中
    # print(_internal_helper()) # ❌ import * 不会导入
    # print(__secret_var) # ❌ 名称已被重整为 _my_module__secret_var

    机制作用域强制程度
    _name (单下划线) from X import * 不导入 弱约定(仍可直接 import)
    __name (双下划线) 名称重整为 _mod__name 中等(需知道重整规则才能访问)
    __all__ 控制 from X import * 的行为 弱(不影响显式 import)
    🆕 9. dataclass 与封装的最佳实践

    Python 3.7+ 的 dataclass 结合 field() 可以写出简洁且封装良好的代码:

    from dataclasses import dataclass, field

    @dataclass
    class ModelConfig:
    """AI 模型配置 —— dataclass 封装示例"""
    model_name: str
    learning_rate: float = 1e-3
    batch_size: int = 32
    _internal_cache: dict = field(default_factory=dict, repr=False, init=False)

    def __post_init__(self):
    """构造后的校验逻辑"""
    if self.learning_rate <= 0:
    raise ValueError("learning_rate must be positive")
    if not self.model_name:
    raise ValueError("model_name cannot be empty")

    @property
    def effective_steps_per_epoch(self):
    """计算属性:根据 batch_size 动态计算"""
    return 100000 // self.batch_size # 假设训练集 100k 样本

    config = ModelConfig("gpt-2", learning_rate=5e-4)
    print(config) # ModelConfig(model_name='gpt-2', learning_rate=0.0005, …)
    print(config.effective_steps_per_epoch) # 3125
    # ModelConfig("", 1e-3) # ❌ ValueError

    dataclass 封装要点:

    • init=False:不在 __init__ 中暴露(类似私有属性)
    • repr=False:不在 __repr__ 中显示(隐藏内部细节)
    • __post_init__:替代 setter 的校验逻辑
    • frozen=True:创建不可变对象(最强封装)
    🆕 10. 反模式:过度封装 (Over-Encapsulation)

    Python 哲学:“We are all consenting adults here.” —— 我们都是成年人,不需要保姆式的保护。

    # ❌ 反模式:Java 风格的过度封装
    class BadEncapsulation:
    def __init__(self, value):
    self.set_value(value)

    def get_value(self):
    return self._value

    def set_value(self, value):
    self._value = value

    # ✅ Pythonic 写法:简单场景直接用公有属性
    class GoodEncapsulation:
    def __init__(self, value):
    self.value = value # 先保持简单

    # 只有当需要在赋值时添加校验/副作用时才升级为 property
    class WhenNeeded:
    def __init__(self, value):
    self._value = value

    @property
    def value(self):
    return self._value

    @value.setter
    def value(self, val):
    if val < 0:
    raise ValueError("must be non-negative")
    self._value = val

    判断是否需要封装的自检清单:

  • 赋值时需要校验吗?→ 需要 → 用 @property
  • 取值时需要计算/缓存吗?→ 需要 → 用 @property
  • 需要控制外部能否删除该属性吗?→ 需要 → 加 @x.deleter
  • 以上都不需要?→ 直接用公有属性,保持简单
  • 🆕 11. AI/ML 框架实战:PyTorch nn.Module 的封装设计

    import torch
    import torch.nn as nn

    class SimpleTransformerBlock(nn.Module):
    """展示 PyTorch 中如何运用封装思想"""

    def __init__(self, d_model: int, n_heads: int, dropout: float = 0.1):
    super().__init__()
    # 私有参数:不希望外部直接修改
    self._d_model = d_model
    self._n_heads = n_heads

    # 公有子模块:PyTorch 约定用 .layer_name 暴露
    self.attention = nn.MultiheadAttention(d_model, n_heads, dropout=dropout)
    self.norm1 = nn.LayerNorm(d_model)
    self.ffn = nn.Sequential(
    nn.Linear(d_model, 4 * d_model),
    nn.GELU(),
    nn.Linear(4 * d_model, d_model),
    )
    self.norm2 = nn.LayerNorm(d_model)
    self.dropout = nn.Dropout(dropout)

    @property
    def head_dim(self) -> int:
    """计算属性:每个注意力头的维度"""
    return self._d_model // self._n_heads

    def forward(self, x, mask=None):
    # 内部实现细节被封装
    attn_out, _ = self.attention(x, x, x, attn_mask=mask)
    x = self.norm1(self.dropout(attn_out) + x)
    ffn_out = self.ffn(x)
    x = self.norm2(self.dropout(ffn_out) + x)
    return x

    block = SimpleTransformerBlock(d_model=512, n_heads=8)
    print(block.head_dim) # 64 — 通过 property 安全访问计算属性
    # block._d_model = 128 # ⚠️ 技术上可以,但违反封装约定

    PyTorch 封装惯例总结:

    • 构造参数 → 存储为私有 (_d_model)
    • 子模块 → 公有属性 (self.attention),便于外部访问和检查
    • 派生量 → @property 计算 (head_dim)
    • 核心逻辑 → forward() 方法,外部只需调用即可

    封装的优势

  • 提高代码的安全性:

    • 封装隐藏了类的内部细节,防止外部对内部属性进行非法操作,减少了数据不一致或无效数据的风险。
  • 提高代码的灵活性:

    • 通过封装,可以灵活地修改类的内部实现,而无需修改类的外部使用代码。这种设计允许类的实现细节发生变化而不影响其接口,具有较高的扩展性。
  • 更好的代码维护性:

    • 封装使得代码更加模块化,每个类或模块只暴露必要的接口,减少了耦合性,增强了代码的可维护性。
  • 控制属性访问:

    • 通过 getter 和 setter 方法,可以控制对属性的访问和修改操作,确保类的内部状态始终有效。
  • 🆕 5. 内存效率提升:__slots__ 可显著降低大量小对象的内存占用。

    🆕 6. 接口稳定性:模块级封装(__all__)保证公共 API 的向后兼容。

    封装与其他 OOP 概念的关系

    • 封装与继承:封装可以结合继承一起使用,通过子类继承父类的公有方法和受保护的属性,封装性依然得以保持。
    • 封装与多态:封装和多态相辅相成,封装允许将实现隐藏,而多态允许对象在运行时决定具体调用的实现,使得代码的扩展性更强。
    • 🆕 封装与组合:良好的封装使得对象可以作为"黑盒子"被组合到更大的系统中,是组合优于继承的基础。

    介绍一下Python中的继承(Inheritance)思想

    继承是面向对象编程(OOP)的一个核心概念,它允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和方法。子类可以继承父类的特性,并且可以在此基础上添加自己的新特性,从而实现代码的重用和扩展。Python 作为一门支持面向对象编程的语言,提供了强大的继承机制。

    Python中继承的优势:

  • 代码重用:子类可以直接使用父类已经定义的方法和属性,避免了重复编写相同的代码片段。
  • 可扩展性:子类可以在不修改父类的情况下,添加新的属性和方法,从而使得代码更具可扩展性。这样可以在不影响父类的基础上,为程序添加新的功能。
  • 一、继承的基本概念

    1. 父类(基类)
    • 定义:被继承的类,提供基本的属性和方法。
    • 作用:作为子类的模板,子类可以继承父类的属性和方法。
    2. 子类(派生类)
    • 定义:从父类继承而来的类,可以新增或重写父类的方法和属性。
    • 作用:在继承父类的基础上进行扩展或修改,实现特定的功能。
    3. 继承的目的
    • 代码重用:避免重复编写相同的代码,提高开发效率。
    • 可扩展性:通过继承,子类可以扩展父类的功能。
    • 多态性:同一个方法在不同的类中可能有不同的实现,增强程序的灵活性。

    二、Python 中的继承实现

    1. 基本语法

    在 Python 中,继承通过在类定义时指定父类来实现。

    class 子类名(父类名):
    # 类的定义

    2. 示例

    父类:

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

    def speak(self):
    pass

    子类:

    class Dog(Animal):
    def speak(self):
    return f"{self.name} says Woof!"

    class Cat(Animal):
    def speak(self):
    return f"{self.name} says Meow!"

    使用子类:

    dog = Dog("Buddy")
    cat = Cat("Kitty")

    print(dog.speak()) # 输出: Buddy says Woof!
    print(cat.speak()) # 输出: Kitty says Meow!

    三、继承的类型

    1. 单继承
    • 定义:一个子类只继承一个父类。

    • 示例:

      class Parent:
      pass

      class Child(Parent):
      pass

    2. 多重继承
    • 定义:一个子类继承多个父类。

    • 语法:

      class 子类名(父类1, 父类2, …):
      pass

    • 示例:

      class Flyable:
      def fly(self):
      return "I can fly!"

      class Swimmable:
      def swim(self):
      return "I can swim!"

      class Duck(Flyable, Swimmable):
      pass

      duck = Duck()
      print(duck.fly()) # 输出: I can fly!
      print(duck.swim()) # 输出: I can swim!

    3. 多层继承
    • 定义:子类继承父类,父类再继承其父类,形成继承链。

    • 示例:

      class GrandParent:
      pass

      class Parent(GrandParent):
      pass

      class Child(Parent):
      pass

    四、方法重写(Override)

    • 定义:子类重新定义父类的同名方法,以实现不同的功能。
    • 作用:让子类能够根据需要修改或扩展父类的方法行为。
    示例:

    class Vehicle:
    def move(self):
    print("The vehicle is moving.")

    class Car(Vehicle):
    def move(self):
    print("The car is driving on the road.")

    vehicle = Vehicle()
    car = Car()

    vehicle.move() # 输出: The vehicle is moving.
    car.move() # 输出: The car is driving on the road.

    五、调用父类的方法

    • 使用 super() 函数:在子类中调用父类的方法或初始化父类。

    • 语法:

      class 子类名(父类名):
      def 方法名(self, 参数):
      super().方法名(参数)

    示例:

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

    class Employee(Person):
    def __init__(self, name, employee_id):
    super().__init__(name) # 调用父类的构造函数
    self.employee_id = employee_id

    employee = Employee("Alice", "E123")
    print(employee.name) # 输出: Alice
    print(employee.employee_id) # 输出: E123

    六、继承中的特殊方法

    1. __init__ 构造函数
    • 继承特性:子类的 __init__ 方法会覆盖父类的 __init__ 方法。
    • 注意:如果子类定义了 __init__ 方法,需要显式调用父类的 __init__ 方法来初始化父类的属性。

    示例:

    class Parent:
    def __init__(self):
    print("Parent init")

    class Child(Parent):
    def __init__(self):
    super().__init__() # 调用父类的构造函数
    print("Child init")

    child = Child()
    # 输出:
    # Parent init
    # Child init

    2. __str__ 和 __repr__ 方法
    • 作用:定义对象的字符串表示形式。
    • 继承特性:子类可以重写这些方法,提供自定义的字符串表示。

    示例:

    class Animal:
    def __str__(self):
    return "This is an animal."

    class Dog(Animal):
    def __str__(self):
    return "This is a dog."

    dog = Dog()
    print(dog) # 输出: This is a dog.

    七、继承的注意事项

    1. 访问权限
    • Python 中不存在像 Java 或 C++ 那样的访问修饰符(public、private、protected)。

    • 以双下划线 __ 开头的属性或方法被视为私有成员,不能在子类中直接访问。

    • 示例:

      class Parent:
      def __init__(self):
      self.__private_var = 42

      class Child(Parent):
      def get_private_var(self):
      return self.__private_var # 这将引发 AttributeError

      child = Child()
      print(child.get_private_var())

    2. 方法解析顺序(MRO)
    • 在多重继承中,Python 使用**方法解析顺序(Method Resolution Order, MRO)**来确定属性和方法的查找顺序。
    • 可以使用 类名.mro() 查看 MRO 列表。

    示例:

    class A:
    pass

    class B(A):
    pass

    class C(A):
    pass

    class D(B, C):
    pass

    print(D.mro())
    # 输出: [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]


    🆕 八、MRO 与 C3 线性化算法 —— 深入理解

    MRO 不是"魔法",而是由 C3 线性化算法确定的。理解这个算法对于排查多重继承 bug 至关重要。

    C3 算法核心规则

    给定一个类 C 的继承列表 [C, (C1, C2, …, Cn)],MRO 的计算遵循:

  • C 的 MRO = [C] + merge(C1 的 MRO, C2 的 MRO, …, Cn 的 MRO, [C1, C2, …, Cn])
  • merge 操作:取每个列表的头部(第一个元素),如果该元素不在任何列表的尾部(除头部外的其余部分),则取出;否则跳过该列表,尝试下一个
  • 图解:菱形继承的 MRO 计算

    A
    / \\
    B C
    \\ /
    D(B, C)

    class A:
    def who_am_i(self):
    return "A"

    class B(A):
    def who_am_i(self):
    return "B"

    class C(A):
    def who_am_i(self):
    return "C"

    class D(B, C):
    pass

    逐步计算 D 的 MRO:

    L[D] = D + merge(L[B], L[C], BC)
    = D + merge(BA, CA, BC) ← L[B]=[B,A], L[C]=[C,A], BC=[B,C]

    Step 1: 取 B(B 不在任何 tail 中)✅ → [D, B]
    merge(A, CA, C) ← B 已取出,剩余

    Step 2: 取 A → A 在 [C, A] 的 tail 中 ❌ 跳过
    取 C → C 不在任何 tail 中 ✅ → [D, B, C]
    merge(A, A) ← C 已取出

    Step 3: 取 A ✅ → [D, B, C, A]

    最终 L[D] = [D, B, C, A, object]

    print(D.mro())
    # [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]

    d = D()
    print(d.who_am_i()) # "B" — B 在 MRO 中排在 C 前面

    面试重点:为什么 D 的 MRO 是 D→B→C→A 而不是 D→C→B→A?

    因为 D 的定义是 class D(B, C) —— B 在前优先。C3 保证了这个"局部优先序"的同时还满足单调性。


    🆕 九、菱形继承问题 (Diamond Problem)

    当多重继承形成菱形结构时,如何确保祖先类只被初始化一次?

    A (有 __init__)
    / \\
    B C (都调用了 super().__init__())
    \\ /
    D

    传统语言的困境:C++ 中 A 会被初始化两次(B 初始化一次,C 初始化一次)。

    Python 的解决方案:协作式多重继承 (Cooperative MI) + super() 的 MRO 链式调用。

    class A:
    def __init__(self):
    print(" A.__init__")
    self.a_val = "from A"

    class B(A):
    def __init__(self):
    print(" B.__init__ (before super)")
    super().__init__() # 不硬编码 A,而是调用 MRO 中的下一个
    print(" B.__init__ (after super)")
    self.b_val = "from B"

    class C(A):
    def __init__(self):
    print(" C.__init__ (before super)")
    super().__init__()
    print(" C.__init__ (after super)")
    self.c_val = "from C"

    class D(B, C):
    def __init__(self):
    print("D.__init__ (before super)")
    super().__init__() # 一行 super() 触发整条 MRO 链
    print("D.__init__ (after super)")

    print("MRO:", [cls.__name__ for cls in D.mro()])
    d = D()

    # 输出:
    # MRO: ['D', 'B', 'C', 'A', 'object']
    # D.__init__ (before super)
    # B.__init__ (before super)
    # C.__init__ (before super)
    # A.__init__ ← A 只被初始化了一次!
    # C.__init__ (after super)
    # B.__init__ (after super)
    # D.__init__ (after super)

    关键结论:

    • super() 不是"调用父类",而是调用 MRO 中的下一个类
    • 只要每个类的 __init__ 都使用 super().__init__(),就能保证每个类恰好初始化一次
    • 这就是为什么 Python 的 super() 推荐无参调用(Python 3+)

    🆕 十、Mixin 模式 —— Python 多重继承的最佳实践

    Mixin 是一种特殊的类,它:

  • 不单独实例化(通常不定义 __init__)
  • 提供单一功能(单一职责)
  • 通过组合混入到目标类(命名以 Mixin / able 结尾)
  • # ===== 定义 Mixin 类 =====

    class JSONSerializableMixin:
    """提供 JSON 序列化能力"""

    def to_json(self) -> str:
    import json
    # 只序列化公有属性
    data = {k: v for k, v in self.__dict__.items() if not k.startswith('_')}
    return json.dumps(data, default=str)

    class LoggingMixin:
    """提供方法调用日志能力"""

    def log_call(self, method_name: str, *args, **kwargs):
    print(f"[LOG] {self.__class__.__name__}.{method_name} called "
    f"with args={args}, kwargs={kwargs}")

    class ValidationMixin:
    """提供数据校验能力"""

    def validate_required(self, *fields):
    for field in fields:
    if not getattr(self, field, None):
    raise ValueError(f"{field} is required")

    # ===== 目标类通过多重继承组合 Mixin =====

    class User(JSONSerializableMixin, LoggingMixin, ValidationMixin):
    def __init__(self, username, email):
    self.username = username
    self.email = email

    def save(self):
    self.log_call('save')
    self.validate_required('username', 'email')
    print(f"Saving user: {self.to_json()}")

    u = User("alice", "alice@example.com")
    u.save()
    # [LOG] User.save called with args=(), kwargs={}
    # Saving user: {"username": "alice", "email": "alice@example.com"}

    Mixin 命名规范:

    命名风格含义示例
    XxxMixin 明确标识为 Mixin LoggingMixin
    Xxxable 表示具备某种能力 Serializable
    以 _ 开头 表示内部 Mixin _CacheMixin

    Mixin 在 AI 框架中的应用:

    # PyTorch 风格的 Mixin 示例
    class GradientCheckpointingMixin:
    """混合梯度 checkpoint 以节省显存"""

    def enable_gradient_checkpointing(self):
    if hasattr(self, 'forward'):
    original_forward = self.forward

    def checkpointed_forward(*args, **kwargs):
    from torch.utils.checkpoint import checkpoint
    return checkpoint(original_forward, *args, **kwargs,
    use_reentrant=False)

    self.forward = checkpointed_forward
    print(f"[{self.__class__.__name__}] Gradient checkpointing enabled")

    class MyTransformer(GradientCheckpointingMixin, nn.Module):
    def __init__(self):
    super().__init__()
    self.layer = nn.Linear(1024, 1024)

    def forward(self, x):
    return self.layer(x)

    model = MyTransformer()
    model.enable_gradient_checkpointing() # 动态混入能力


    🆕 十一、组合优于继承 (Composition over Inheritance)

    原则:如果两个类之间的关系不是"is-a"(猫是动物),而是"has-a"(汽车有引擎),应该使用组合而非继承。

    # ❌ 反例:滥用继承 —— 引擎并不是一种"汽车"
    class Vehicle:
    def move(self): pass

    class Engine(Vehicle): # Engine is-a Vehicle? 语义错误!
    def start(self): pass

    class Car(Engine): # Car is-a Engine? 更加奇怪
    pass

    # ✅ 正解:使用组合 —— 汽车"拥有"引擎
    class Engine:
    def start(self):
    print("Engine started")

    def stop(self):
    print("Engine stopped")

    class Wheel:
    def rotate(self):
    print("Wheel rotating")

    class Car:
    """Car has-an Engine and has Wheels"""

    def __init__(self):
    self.engine = Engine() # 组合
    self.wheels = [Wheel() for _ in range(4)]

    def start(self):
    self.engine.start()

    def drive(self):
    for wheel in self.wheels:
    wheel.rotate()

    # ===== 决策指南 =====
    #
    # 问自己:X is a Y 吗?
    # ✓ 是 → 用继承 (Dog is an Animal)
    # ✗ 否 → 用组合 (Car has an Engine; Employee has a SalaryCalculator)

    GoF 设计原则原文:

    “Favor ‘object composition’ over ‘class inheritance’. […] Inheritance breaks encapsulation (subclass depends on parent’s implementation details).”

    继承破坏封装性的原因:子类依赖于父类的内部实现细节,父类的改动可能波及所有子类(脆弱基类问题)。


    🆕 十二、__init_subclass__ —— 子类注册与约束钩子

    Python 3.6+ 提供的类钩子,在子类被定义时自动调用(注意:不是在实例化时)。

    class PluginBase:
    """插件基类:自动注册所有子类"""

    _registry = {} # plugin_name -> subclass

    def __init_subclass__(cls, plugin_name=None, **kwargs):
    super().__init_subclass__(**kwargs)
    if plugin_name is None:
    raise TypeError(
    f"Subclass {cls.__name__} must specify 'plugin_name' keyword argument"
    )
    cls._registry[plugin_name] = cls
    cls.plugin_name = plugin_name
    print(f"[Auto-register] Plugin '{plugin_name}' → {cls.__name__}")

    def run(self, data):
    raise NotImplementedError

    # ===== 定义插件子类 =====
    class TextProcessor(PluginBase, plugin_name="text"):
    def run(self, data):
    return data.lower()

    class ImageProcessor(PluginBase, plugin_name="image"):
    def run(self, data):
    return f"[IMAGE processed: {len(data)} bytes]"

    # ===== 使用注册表 =====
    print("Available plugins:", list(PluginBase._registry.keys()))
    # ['text', 'image']

    processor = PluginBase._registry["text"]()
    print(processor.run("HELLO WORLD")) # "hello world"

    实际应用场景:

    • 插件系统:自动发现和注册处理器(如 FastAPI 的路由注册)
    • 契约强制:要求子类实现特定属性或方法
    • ORM 映射:SQLAlchemy 的 declarative_base 就是基于此机制

    🆕 十三、super() 的本质深入

    super() 到底返回什么?

    class A:
    def method(self):
    print("A.method", self)

    class B(A):
    def method(self):
    print("B.method (before super)", self)
    super().method() # super() 返回的是什么?
    print("B.method (after super)", self)

    b = B()
    b.method()

    super() 返回的是一个代理对象 (proxy),它:

  • 记录了当前类的 MRO
  • 记录了当前类在 MRO 中的位置
  • 属性查找从 MRO 的下一个位置开始
  • # 验证 super() 的行为
    print(super(B, b)) # <super: <class 'B'>, <B object>>
    # 这个代理对象在查找 attribute 时,
    # 会从 B 的 MRO 中 B 的下一个类开始找

    为什么推荐无参 super()(Python 3+)?

    • Python 2 必须写 super(ClassName, self)
    • Python 3 的 super() 在类方法内部会自动推断当前类和 self
    • 无参写法在重构类名时不会出错

    super() 的常见陷阱:

    # ⚠️ 陷阱:在类外部使用 super()
    class Parent:
    def __init__(self):
    self.value = 10

    class Child(Parent):
    def __init__(self):
    super().__init__()

    # 在类外(非方法内)不能使用无参 super()
    # super() # RuntimeError: super(): no arguments


    🆕 十四、AI/ML 框架实战:HuggingFace PreTrainedModel 继承体系

    # 简化的 HuggingFace 模型继承链
    from abc import abstractmethod
    from typing import Optional, Dict, Any
    import torch
    import torch.nn as nn

    # ===== 第一层:模型基类 =====
    class PreTrainedModel(nn.Module):
    """HF 所有模型的根类 —— 展示大型项目中的继承设计"""

    config_class = None # 类变量:子类必须覆写
    base_model_prefix = "model"

    def __init__(self, config):
    super().__init__()
    self.config = config

    @abstractmethod
    def forward(
    self,
    input_ids: Optional[torch.Tensor] = None,
    attention_mask: Optional[torch.Tensor] = None,
    **kwargs
    ) -> Dict[str, torch.Tensor]:
    """所有子类必须实现 forward"""

    def save_pretrained(self, output_dir):
    """通用保存逻辑 —— 所有子类共享,无需各自实现"""
    print(f"[Save] Saving {self.__class__.__name__} to {output_dir}")
    # 保存 config.json + model.safetensors
    self.config.save_pretrained(output_dir)
    torch.save(self.state_dict(), f"{output_dir}/model.pt")

    @classmethod
    def from_pretrained(cls, pretrained_path):
    """通用加载逻辑 —— 工厂方法模式"""
    print(f"[Load] Loading {cls.__name__} from {pretrained_path}")
    config = cls.config_class.from_pretrained(pretrained_path)
    model = cls(config)
    state_dict = torch.load(f"{pretrained_path}/model.pt")
    model.load_state_dict(state_dict)
    return model

    def gradient_checkpointing_enable(self):
    """可选能力:通过 mixin 或直接定义"""
    self.gradient_checkpointing = True

    # ===== 第二层:具体模型 =====
    class GPT2Model(PreTrainedModel):
    """GPT-2 模型 —— 继承通用能力,专注自身逻辑"""

    config_class = GPT2Config # 绑定配置类

    def __init__(self, config: GPT2Config):
    super().__init__(config)
    # GPT-2 特有的层结构
    self.wte = nn.Embedding(config.vocab_size, config.hidden_size)
    self.wpe = nn.Embedding(config.max_position_embeddings, config.hidden_size)
    self.h = nn.ModuleList([
    GPT2Block(config) for _ in config.num_hidden_layers
    ])
    self.ln_f = nn.LayerNorm(config.hidden_size)

    def forward(self, input_ids, attention_mask=None, **kwargs):
    # GPT-2 特有的前向传播逻辑
    positions = torch.arange(input_ids.shape[-1])
    hidden_states = self.wte(input_ids) + self.wpe(positions)

    for block in self.h:
    hidden_states = block(hidden_states, attention_mask)

    hidden_states = self.ln_f(hidden_states)
    return {"last_hidden_state": hidden_states}

    # ===== 第三层:带任务头的派生类 =====
    class GPT2ForSequenceClassification(GPT2Model):
    """GPT-2 文本分类 —— 继承 GPT2Model,添加分类头"""

    def __init__(self, config):
    super().__init__(config)
    # 新增:分类头(组合)
    self.classifier = nn.Linear(config.hidden_size, config.num_labels)
    self.num_labels = config.num_labels

    def forward(self, input_ids, attention_mask=None, labels=None, **kwargs):
    # 复用父类的 transformer 编码
    outputs = super().forward(input_ids, attention_mask, **kwargs)
    logits = self.classifier(outputs["last_hidden_state"][:, -1, :])

    loss = None
    if labels is not None:
    loss_fn = nn.CrossEntropyLoss()
    loss = loss_fn(logits, labels)

    return {"loss": loss, "logits": logits}

    # ===== 使用示例 =====
    model = GPT2ForSequenceClassification.from_pretrained("./my-gpt2-classifier")
    result = model(input_ids=torch.tensor([[101, 202, 303]]))
    model.save_pretrained("./updated-model") # 继承自 PreTrainedModel 的通用方法

    这个继承体系的设计亮点:

    层次类职责继承的价值
    L1 PreTrainedModel 通用能力(save/load/checkpoint) 子类免费获得
    L2 GPT2Model 模型特有结构 复用 L1,专注自身
    L3 GPT2ForSeqCls 任务头 复用 L1+L2,只加分类头

    介绍一下Python中的多态(Polymorphism)思想

    多态(Polymorphism) 是面向对象编程(OOP)的核心概念之一,指的是同一操作作用于不同对象时,能够产生不同的解释和行为。简单来说,多态允许我们在不考虑对象具体类型的情况下,对不同类型的对象执行相同的操作。在 Python 中,多态性通过动态类型和灵活的对象模型得以实现。

    一、什么是多态?

    1. 定义
    • 多态性(Polymorphism):源自希腊语,意为"多种形式"。在编程中,它指的是同一操作在不同对象上具有不同的行为。
    2. 多态的类型(🆕 补充完整分类)

    多态在编程语言理论中有三种基本形式:

    多态类型实现方式Python 支持情况
    Ad-hoc 多态(特殊多态) 方法重载、运算符重载 运算符重载 ✅;方法重载用默认参数模拟 ✅
    子类型多态(Subtype) 继承 + 方法重写 核心 ✅
    参数多态(Parametric) 泛型 / TypeVar typing.Generic ✅

    此外还有两种重要的变体:

    多态类型实现方式Python 支持情况
    泛型函数多态 singledispatch(按类型分派函数) functools.singledispatch ✅
    结构化子类型 Protocol(结构匹配而非继承) typing.Protocol ✅ (3.8+)

    二、Python 中的多态实现

    1. 动态类型和鸭子类型
    • 动态类型:Python 是动态类型语言,变量的类型在运行时确定。这使得多态性更自然。
    • 鸭子类型(Duck Typing):只要对象具有所需的方法或属性,就可以使用,无需关心对象的具体类型。

    示例:

    class Dog:
    def speak(self):
    return "Woof!"

    class Cat:
    def speak(self):
    return "Meow!"

    class Duck:
    def speak(self):
    return "Quack!"

    def animal_speak(animal):
    return animal.speak()

    animals = [Dog(), Cat(), Duck()]
    for animal in animals:
    print(animal_speak(animal))

    输出:

    Woof!
    Meow!
    Quack!

    • 解释:animal_speak 函数可以接受任何具有 speak 方法的对象,而不关心其具体类型。这就是鸭子类型的体现。
    2. 继承和方法重写
    • 继承:子类继承父类的方法和属性。
    • 方法重写(Override):子类可以重写父类的方法,实现不同的行为。

    示例:

    class Animal:
    def speak(self):
    raise NotImplementedError("Subclasses must implement this method.")

    class Dog(Animal):
    def speak(self):
    return "Woof!"

    class Cat(Animal):
    def speak(self):
    return "Meow!"

    def animal_speak(animal):
    return animal.speak()

    animals = [Dog(), Cat()]
    for animal in animals:
    print(animal_speak(animal))

    输出:

    Woof!
    Meow!

    • 解释:Animal 类定义了一个抽象方法 speak,子类 Dog 和 Cat 分别实现了自己的版本。animal_speak 函数调用时,根据传入对象的类型执行对应的方法。
    3. 运算符重载
    • 运算符重载:在类中定义特殊方法,实现对内置运算符的重载。

    示例:

    class Vector:
    def __init__(self, x, y):
    self.x = x
    self.y = y

    # 重载加法运算符
    def __add__(self, other):
    return Vector(self.x + other.x, self.y + other.y)

    # 重载字符串表示
    def __str__(self):
    return f"Vector({self.x}, {self.y})"

    v1 = Vector(2, 3)
    v2 = Vector(5, 7)
    v3 = v1 + v2
    print(v3)

    输出:

    Vector(7, 10)

    • 解释:通过定义 __add__ 方法,实现了 Vector 对象的加法运算。这是 Python 的另一种多态形式。

    三、鸭子类型详解

    1. 概念
    • 鸭子类型:如果一只鸟走起来像鸭子、游泳像鸭子、叫声像鸭子,那么这只鸟可以被称为鸭子。
    • 在 Python 中:只要对象具有所需的方法或属性,就可以将其视为某种类型。

    示例:

    class Bird:
    def fly(self):
    print("Bird is flying.")

    class Airplane:
    def fly(self):
    print("Airplane is flying.")

    class Fish:
    def swim(self):
    print("Fish is swimming.")

    def lift_off(entity):
    entity.fly()

    bird = Bird()
    plane = Airplane()
    fish = Fish()

    lift_off(bird) # 输出: Bird is flying.
    lift_off(plane) # 输出: Airplane is flying.
    # lift_off(fish) # AttributeError: 'Fish' object has no attribute 'fly'

    • 解释:lift_off 函数可以接受任何具有 fly 方法的对象。Fish 对象由于没有 fly 方法,调用时会抛出 AttributeError。

     2. 安全的鸭子类型:动态检查与容错

    工程实践中,鸭子类型常配合动态检查使用:

    def safe_fly(entity):
    """安全的鸭子类型调用:先检查再调用"""
    if hasattr(entity, 'fly'):
    entity.fly()
    elif hasattr(entity, 'swim'):
    print(f"{entity.__class__.__name__} can't fly, but can swim.")
    else:
    raise TypeError(f"{entity.__class__.__name__} is neither flyable nor swimmable")

    # 或者用 getattr 提供默认值
    def get_or_default(obj, attr, default=None):
    """获取属性,不存在时返回默认值而不是抛异常"""
    return getattr(obj, attr, default) or default

    # EAFP 风格(Easier to Ask Forgiveness than Permission)
    def eager_fly(entity):
    try:
    entity.fly()
    except AttributeError:
    print(f"{entity.__class__.__name__}: cannot fly")

    三种鸭子类型安全策略对比:

    策略风格适用场景
    hasattr() + if LBYL (Look Before You Leap) 有明确的 fallback 分支
    getattr(obj, attr, default) LBYL 需要提供默认值的场景
    try/except AttributeError EAFP 异常路径极少的情况(更快)

    四、多态性的优点

    1. 提高代码的灵活性
    • 可以编写与特定类型无关的代码,处理不同类型的对象。
    2. 增强代码的可扩展性
    • 添加新类型的对象时,无需修改现有代码,只需确保新对象实现了所需的方法。
    3. 代码重用
    • 通过多态,可以编写通用的函数或方法,避免重复代码。

    五、抽象基类(Abstract Base Class)

    • 概念:抽象基类定义了接口规范,子类必须实现特定的方法。
    • 作用:确保子类实现必要的方法,提供一致的接口。

    示例:

    from abc import ABC, abstractmethod

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

    class Rectangle(Shape):
    def __init__(self, width, height):
    self.width = width
    self.height = height

    def area(self):
    return self.width * self.height

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

    def area(self):
    return 3.1416 * self.radius ** 2

    shapes = [Rectangle(3, 4), Circle(5)]
    for shape in shapes:
    print(f"Area: {shape.area()}")

    输出:

    Area: 12
    Area: 78.53999999999999

    • 解释:Shape 是一个抽象基类,定义了 area 方法。Rectangle 和 Circle 实现了该方法。通过多态,可以统一处理不同形状的面积计算。

    六、Python 中不支持方法重载

    • 说明:在 Python 中,方法重载(相同方法名,不同参数)并不被支持。后定义的方法会覆盖先前的方法。
    • 替代方案:使用默认参数或可变参数。

    示例:

    class MathOperations:
    def multiply(self, x, y, z=None):
    if z is not None:
    return x * y * z
    else:
    return x * y

    math_ops = MathOperations()
    print(math_ops.multiply(2, 3)) # 输出: 6
    print(math_ops.multiply(2, 3, 4)) # 输出: 24

    • 解释:通过使用默认参数,实现类似方法重载的效果。

    🆕 补充:typing.overload —— 静态类型层面的方法重载

    虽然运行时不支持重载,但 Python 3.5+ 的 typing.overload 允许在静态类型检查时声明多个签名:

    from typing import overload, Union

    class Processor:
    @overload
    def process(self, data: str) -> str: …
    @overload
    def process(self, data: list[str]) -> list[str]: …
    @overload
    def process(self, data: None) -> None: …

    def process(self, data):
    if isinstance(data, str):
    return data.upper()
    elif isinstance(data, list):
    return [item.upper() for item in data]
    elif data is None:
    return None
    raise TypeError("Unsupported type")

    # mypy / pyright 能正确推断每种调用的返回类型
    p = Processor()
    result1: str = p.process("hello") # 类型检查通过
    result2: list[str] = p.process(["a"]) # 类型检查通过
    result3: None = p.process(None) # 类型检查通过

    注意:overload 只影响类型检查器的推断,运行时仍然只有一个 process 方法。它是给 IDE 和 mypy 用的"类型注解重载"。

    七、方法解析顺序(MRO)在多态中的作用

    • MRO(Method Resolution Order):在多重继承中,Python 按照 MRO 决定调用哪个类的方法。
    • 多态与 MRO:当子类继承多个父类,且父类中有同名方法时,MRO 决定了方法的调用顺序。

    示例:

    class A:
    def do_something(self):
    print("Method from A")

    class B:
    def do_something(self):
    print("Method from B")

    class C(B, A):
    pass

    c = C()
    c.do_something()
    print(C.mro())

    输出:

    Method from B
    [<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

    • 解释:C 继承了 B 和 A,由于 B 在前,调用同名方法时,B 的方法优先。

    🆕 八、typing.Protocol —— 结构化子类型多态

    Python 3.8+ 引入的 typing.Protocol 是对传统 ABC 多态的重要补充。它实现了结构化子类型 (Structural Subtyping):不需要显式继承,只要结构(方法/属性)匹配即可。

    ABC vs Protocol 核心对比

    # ===== 名义子类型 (Nominal) —— 传统 ABC =====
    from abc import ABC, abstractmethod

    class Flyer(ABC):
    @abstractmethod
    def fly(self) -> str: …

    class Bird(Flyer): # 必须显式继承 Flyer
    def fly(self) -> str:
    return "Bird flies"

    def let_it_fly(flyer: Flyer): # 只接受 Flyer 及其子类
    return flyer.fly()

    let_it_fly(Bird()) # ✅
    let_it_fly(Airplane()) # ❌ Airplane 没有继承 Flyer

    # ===== 结构化子类型 (Structural) —— Protocol =====
    from typing import Protocol, runtime_checkable

    @runtime_checkable
    class Flyable(Protocol): # Protocol 是接口约定,不是基类
    def fly(self) -> str: …

    class Bird:
    def fly(self) -> str:
    return "Bird flies"

    class Airplane: # 无需继承任何东西!
    def fly(self) -> str:
    return "Airplane flies"

    def let_it_fly_cool(entity: Flyable): # 接受任何有 fly() 方法的对象
    return entity.fly()

    let_it_fly_cool(Bird()) # ✅ 结构匹配
    let_it_fly_cool(Airplane()) # ✅ 结构匹配

    完整对比表
    维度ABC (名义子类型)Protocol (结构化子类型)
    匹配依据 继承关系(is-a) 结构兼容(has-a-method)
    是否需要继承 ✅ 必须 ❌ 不需要
    运行时检查 isinstance(x, AbcClass) 需要 @runtime_checkable + isinstance
    类型检查器 原生支持 mypy/pyright 支持
    适用场景 强契约、框架基类 解耦、跨库接口、鸭子类型安全化
    性能开销 @runtime_checkable 有少量开销
    Python 版本 全部 3.8+
    Protocol 高级用法:复合协议

    from typing import Protocol, TypeVar

    T = TypeVar('T')

    class Serializable(Protocol):
    def to_bytes(self) -> bytes: …

    class Hashable(Protocol):
    def hash(self) -> int: …

    # 复合协议:同时满足两个协议
    class SerializableAndHashable(Serializable, Hashable, Protocol):
    pass

    def cache_and_serialize(obj: SerializableAndHashable) -> bytes:
    """只接受同时可序列化和可哈希的对象"""
    key = obj.hash()
    print(f"Caching with key={key}")
    return obj.to_bytes()

    AI 框架中的 Protocol 应用:

    # 定义模型接口协议 —— 不绑定任何基类
    class HasForward(Protocol):
    config: object # 需要有 config 属性

    def forward(self, x: torch.Tensor) -> torch.Tensor: …

    class HasLoss(Protocol):
    def compute_loss(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: …

    # 任何符合结构的模型都可以传入,无论来自哪个库
    def train_step(model: HasForward & HasLoss, batch):
    pred = model.forward(batch['input'])
    loss = model.compute_loss(pred, batch['target'])
    loss.backward()
    return loss


    🆕 九、functools.singledispatch —— 泛型函数多态

    @singledispatch 实现了一种不同于继承的多态形式:根据第一个参数的运行时类型,将函数调用分派到不同的实现。

    from functools import singledispatch

    @singledispatch
    def serialize(data):
    """默认实现:未知类型转字符串"""
    return str(data)

    @serialize.register(int)
    def _(data: int):
    return f"INT:{data}"

    @serialize.register(float)
    def _(data: float):
    return f"FLOAT:{data:.2f}"

    @serialize.register(list)
    def _(data: list):
    return f"LIST:[{','.join(serialize(x) for x in data)}]"

    @serialize.register(dict)
    def _(data: dict):
    pairs = [f"{k}={serialize(v)}" for k, v in data.items()]
    return f"DICT:{{{','.join(pairs)}}}"

    # ===== 使用 =====
    print(serialize(42)) # INT:42
    print(serialize(3.14)) # FLOAT:3.14
    print(serialize([1, 2.5, "hello"])) # LIST:[INT:1,FLOAT:2.5,hello]
    print(serialize({"a": 1, "b": [2]})) # DICT:{a=INT:1,b=LIST:[INT:2]}

    与继承多态的关键区别:

    维度继承多态singledispatch 多态
    分发依据 对象自身的类型(self) 函数第一个参数的类型
    扩展方式 创建新子类 @func.register(NewType)
    耦合度 对象需要知道自己的类 对象完全不知道被分发
    适用场景 行为主要由对象自身决定 同一数据有多种表示格式

    AI/ML 中的典型应用:

    @singledispatch
    def to_tensor(data):
    raise TypeError(f"Cannot convert {type(data).__name__} to tensor")

    @to_tensor.register(torch.Tensor)
    def _(data):
    return data # 已经是 tensor

    @to_tensor.register(np.ndarray)
    def _(data):
    return torch.from_numpy(data)

    @to_tensor.register(list)
    def _(data):
    return torch.tensor(data)

    @to_tensor.register(pd.DataFrame)
    def _(data):
    return torch.tensor(data.values)


    🆕 十、参数多态 (Parametric Polymorphism) —— TypeVar 与 Generic

    参数多态是指函数或类在不指定具体类型的情况下工作,由调用者决定类型。

    from typing import TypeVar, Generic, List

    T = TypeVar('T') # 类型变量

    # ===== 泛型函数 =====
    def first_item(items: List[T]) -> T:
    """适用于任何元素类型的列表"""
    return items[0]

    int_list = [1, 2, 3]
    str_list = ["a", "b", "c"]

    a: int = first_item(int_list) # 类型推断 T = int
    b: str = first_item(str_list) # 类型推断 T = str

    # ===== 泛型类 =====
    class Stack(Generic[T]):
    """类型安全的栈 —— 同一份代码适用于任何 T"""

    def __init__(self):
    self._items: List[T] = []

    def push(self, item: T) -> None:
    self._items.append(item)

    def pop(self) -> T:
    return self._items.pop()

    def peek(self) -> T:
    return self._items[-1]

    int_stack = Stack[int]()
    int_stack.push(1)
    int_stack.push(2)
    x: int = int_stack.pop() # 类型检查器知道 pop() 返回 int

    str_stack = Stack[str]()
    str_stack.push("hello")
    y: str = str_stack.pop() # 类型检查器知道 pop() 返回 str

    有界类型参数 (Bounded TypeVar):

    from typing import TypeVar

    class Shape:
    def area(self) -> float: …

    class Rectangle(Shape):
    def __init__(self, w, h): self.w, self.h = w, h
    def area(self) -> float: return self.w * self.h

    class Circle(Shape):
    def __init__(self, r): self.r = r
    def area(self) -> float: return 3.14 * self.r ** 2

    S = TypeVar('S', bound=Shape) # S 必须是 Shape 或其子类

    def total_area(shapes: list[S]) -> float:
    """泛型函数 + 有界类型参数 = 类型安全的多态"""
    return sum(s.area() for s in shapes)

    total_area([Rectangle(3, 4), Circle(5)]) # ✅ 返回 float
    # total_area([1, 2, 3]) # ❌ 类型错误:int 不满足 bound=Shape


    🆕 十一、__call__ —— 可调用对象多态

    Python 中一切皆对象,包括函数本身。__call__ 方法让任何对象都可以像函数一样被调用,这是极其强大的多态形式。

    # ===== 不同形式的"可调用物" =====

    def plain_function(x):
    return x * 2

    class Multiplier:
    """可调用对象 —— 行为与函数一致"""

    def __init__(self, factor):
    self.factor = factor

    def __call__(self, x):
    return x * self.factor

    class Exponential:
    """另一个可调用对象 —— 相同接口,不同行为"""

    def __call__(self, x):
    return x ** 2

    # ===== 统一调用 =====
    def apply_operation(value, operation):
    """operation 可以是函数、lambda、可调用对象 —— 多态"""
    return operation(value)

    print(apply_operation(5, plain_function)) # 10 (函数)
    print(apply_operation(5, Multiplier(3))) # 15 (可调用对象)
    print(apply_operation(5, Exponential())) # 25 (可调用对象)
    print(apply_operation(5, lambda x: x + 1)) # 6 (lambda)

    AI/ML 框架中的 __call__ 多态:

    import torch
    import torch.nn as nn

    # PyTorch 中,模型、层、损失函数、激活函数全部是 callable
    model = nn.Linear(10, 5) # 层
    activation = nn.ReLU() # 激活函数
    loss_fn = nn.MSELoss() # 损失函数

    x = torch.randn(4, 10)

    # 统一的调用接口 —— 全部使用 __call__
    x = model(x) # 等价于 model.__call__(x)
    x = activation(x) # 等价于 activation.__call__(x)
    # loss = loss_fn(pred, target) # 等价于 loss_fn.__call__(pred, target)

    # ===== 自定义 callable 模块 =====
    class Preprocessor(nn.Module):
    """预处理 + 模型的组合 pipeline"""

    def __init__(self, vocab_size, embed_dim):
    super().__init__()
    self.embedding = nn.Embedding(vocab_size, embed_dim)
    self.dropout = nn.Dropout(0.1)

    def forward(self, input_ids):
    return self.dropout(self.embedding(input_ids))

    # 让它既可以用 forward() 也可以直接调用
    __call__ = forward

    preprocessor = Preprocessor(vocab_size=30000, embed_dim=768)
    tokens = torch.tensor([[1, 2, 3, 4]])
    vectors = preprocessor(tokens) # 通过 __call__ 调用

    __call__ 多态的本质:

    • 函数、bound method、lambda、functools.partial、__call__ 对象实现了统一的调用协议
    • callable(obj) 检查对象是否可调用
    • 这是 Python 中最高层次的多态之一

    🆕 十二、协议多态 (Protocol-based Polymorphism) 概览

    Python 中除了方法调用多态,还有多种隐式协议构成的多态:

    # ===== 1. 迭代器协议 =====
    class Counter:
    def __init__(self, limit):
    self.limit = limit
    self.count = 0

    def __iter__(self):
    return self

    def __next__(self):
    if self.count >= self.limit:
    raise StopIteration
    self.count += 1
    return self.count

    # for 循环不关心对象类型,只要实现了 __iter__/__next__
    for i in Counter(5):
    print(i, end=' ') # 1 2 3 4 5

    # ===== 2. 上下文管理器协议 =====
    class Timer:
    def __enter__(self):
    import time
    self.start = time.time()
    return self

    def __exit__(self, exc_type, exc_val, exc_tb):
    import time
    elapsed = time.time() – self.start
    print(f"\\nElapsed: {elapsed:.3f}s")
    return False

    # with 语句不关心对象类型,只要实现了 __enter__/__exit__
    with Timer():
    sum(range(10_000_000))

    # ===== 3. 容器协议 =====
    class SparseVector:
    def __init__(self, data):
    self._data = {i: v for i, v in enumerate(data) if v != 0}

    def __len__(self):
    return max(self._data) + 1 if self._data else 0

    def __getitem__(self, index):
    return self._data.get(index, 0)

    sv = SparseVector([0, 0, 5, 0, 8])
    print(len(sv)) # 5 (__len__)
    print(sv[2]) # 5 (__getitem__)
    print(sv[4]) # 8
    print(sv[0]) # 0 (默认值)

    协议多态的意义:Python 的 for / with / [] / len() 等语法对所有实现了相应协议的对象都有效——这就是内置的多态基础设施。


    🆕 十三、AI/ML 框架实战:多态在推理引擎中的应用

    """模拟一个多后端推理引擎 —— 展示多态在实际 AI 工程中的综合运用"""
    from abc import ABC, abstractmethod
    from typing import Protocol, Union
    from functools import singledispatch
    import numpy as np

    # ===== 1. 抽象基类定义接口 =====
    class InferenceBackend(ABC):
    """推理后端的抽象基类 —— 名义子类型多态"""

    @abstractmethod
    def load_model(self, model_path: str) -> None:

    @abstractmethod
    def infer(self, input_data: np.ndarray) -> np.ndarray:

    @abstractmethod
    def benchmark(self, iterations: int = 100) -> dict:

    # ===== 2. 具体后端实现 =====
    class ONNXBackend(InferenceBackend):
    """ONNX Runtime 后端"""

    def load_model(self, model_path: str):
    print(f"[ONNX] Loading model from {model_path}")
    self.model = f"onnx_model:{model_path}"

    def infer(self, input_data: np.ndarray):
    print(f"[ONNX] Running inference on shape {input_data.shape}")
    return input_data * 0.5 + 0.1 # 模拟推理

    def benchmark(self, iterations: int = 100) -> dict:
    return {"backend": "onnx", "avg_ms": 2.3, "iterations": iterations}

    class TensorRTBackend(InferenceBackend):
    """TensorRT 后端"""

    def load_model(self, model_path: str):
    print(f"[TRT] Loading engine from {model_path}")
    self.engine = f"trt_engine:{model_path}"

    def infer(self, input_data: np.ndarray):
    print(f"[TRT] Running inference on shape {input_data.shape}")
    return input_data * 0.5 + 0.1

    def benchmark(self, iterations: int = 100) -> dict:
    return {"backend": "tensorrt", "avg_ms": 0.8, "iterations": iterations}

    class VLLMBackend(InferenceBackend):
    """vLLM 后端 (LLM 推理)"""

    def load_model(self, model_path: str):
    print(f"[vLLM] Loading LLM from {model_path}")
    self.llm = f"vllm:{model_path}"

    def infer(self, input_data: np.ndarray):
    print(f"[vLLM] Generating tokens for batch {input_data.shape}")
    # 返回 token ids
    return np.random.randint(0, 32000, size=(input_data.shape[0], 20))

    def benchmark(self, iterations: int = 100) -> dict:
    return {"backend": "vllm", "avg_ms_p3": 45.2, "iterations": iterations}

    # ===== 3. 推理引擎 —— 使用多态 =====
    class InferenceEngine:
    """
    多态应用的综合展示:
    – 子类型多态:不同后端统一接口
    – Protocol 多态:输入数据可以是各种格式
    – singledispatch:不同输出格式的序列化
    """

    def __init__(self, backend: InferenceBackend):
    self.backend = backend

    def run(self, input_data: np.ndarray) -> np.ndarray:
    """统一入口 —— 不关心具体是哪种后端"""
    return self.backend.infer(input_data)

    def run_benchmark(self) -> dict:
    return self.backend.benchmark()

    def switch_backend(self, new_backend: InferenceBackend):
    """切换后端 —— 多态让切换零成本"""
    self.backend = new_backend

    # ===== 4. singledispatch:输出格式多态 =====
    @singledispatch
    def format_result(result):
    return str(result)

    @format_result.register(np.ndarray)
    def _(result: np.ndarray):
    return f"ndarray(shape={result.shape}, dtype={result.dtype})"

    @format_result.register(dict)
    def _(result: dict):
    return " | ".join(f"{k}={v}" for k, v in result.items())

    # ===== 5. 使用示例 =====
    engine = InferenceEngine(ONNXBackend())
    engine.backend.load_model("model.onnx")

    data = np.random.randn(1, 3, 224, 224)
    result = engine.run(data)
    print(format_result(result))

    print(format_result(engine.run_benchmark()))

    # 切换到 TensorRT —— 接口不变,行为改变
    engine.switch_backend(TensorRTBackend())
    engine.backend.load_model("model.engine")
    result = engine.run(data)
    print(format_result(result))
    print(format_result(engine.run_benchmark()))

    这个案例综合运用了四种多态形式:

    多态形式在案例中的体现
    子类型多态 (ABC) InferenceBackend + 三个具体后端
    结构化子类型 (Protocol) 输入数据可扩展为任何 HasArray 协议
    泛型函数多态 (singledispatch) format_result 按类型选择序列化方式
    协议多态 InferenceEngine 对后端的无感知切换

    第三部分:总结 —— 三节内容的关联与进阶路线

    三者的关系图谱

    ┌─────────────────────────────────────────────────────┐
    │ 面向对象编程 (OOP) │
    │ │
    │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
    │ │ 封装 │ │ 继承 │ │ 多态 │ │
    │ │ Encaps. │ │ Inherit. │ │ Poly. │ │
    │ ├──────────┤ ├──────────┤ ├──────────┤ │
    │ │ public │ │ 单继承 │ │ 子类型 │ │
    │ │ private │ │ 多重继承 │ │ 鸭子类型 │ │
    │ │ protected│ │ 多层继承 │ │ 运算符重载│ │
    │ │ property │ │ Mixin │ │ Protocol │ │
    │ │ 描述符 │ │ super() │ │ singledisp│ │
    │ │ __slots_│ │ __init_ │ │ 参数多态 │ │
    │ │ 模块封装 │ │ subclass │ │ __call__ │ │
    │ │ dataclass│ │ 组合优于 │ │ 协议多态 │ │
    │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
    │ │ │ │ │
    │ └──────────────┼──────────────┘ │
    │ ▼ │
    │ ┌──────────────┐ │
    │ │ 设计模式 │ │
    │ │ 工厂/策略/模板 │ │
    │ │ 观察者/命令 │ │
    │ └──────────────┘ │
    └─────────────────────────────────────────────────────┘

    面试回答策略建议

    问题回答要点对应本节内容
    “什么是封装?” 命名约定 + property + 描述符 + __slots__ + 模块级封装
    “Python 的私有是真正的私有吗?” name mangling + “consenting adults” 哲学
    “MRO 是什么?” C3 线性化 + 菱形继承 + super() 本质
    “什么时候用 Mixin?” 单一功能 + 不单独实例化 + 命名规范
    “组合 vs 继承?” is-a 用继承,has-a 用组合 + 脆弱基类问题
    “Python 有几种多态?” 四种:子类型/鸭子/参数/泛型函数
    “ABC vs Protocol?” 名义 vs 结构 + 表格对比
    “如何在 AI 项目中运用 OOP?” PyTorch/HF 继承体系 + 推理引擎多态

    文档结束。本文档共包含:

    • 第17节:11 个子节(原文 5 个 + 🆕 6 个新增)
    • 第18节:14 个子节(原文 7 个 + 🆕 7 个新增)
    • 第19节:13 个子节(原文 7 个 + 🆕 6 个新增)
    • 新增 AI/ML 实战案例 4 个(PyTorch 封装、HF 继承体系、推理引擎多态、Mixin 梯度 checkpoint)
    赞(0)
    未经允许不得转载:171主机测评 » Python.三.(三)--3. 面向对象三大特征
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址