三、 函数、类与面向对象编程 (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)
--3. 面向对象三大特征&spm=1001.2101.3001.5002&articleId=163572239&d=1&t=3&u=4f02b372559c4548942194a2b2adbe29)
1万+

被折叠的 条评论
为什么被折叠?



