在 Python 3.5+ 中引入了类型提示(Type Hints)之后。
这些功能可以帮助开发者在编写代码时更明确地指定变量、函数参数和返回值的类型,从而提高代码的可读性和可维护性。
作为
- 允许定义可操作多种类型的函数、类或容器。
- 增强代码可读性,配合类型检查工具(如 mypy)实现静态类型验证。
类型变量
Python 的 typing 模块提供了 TypeVar,用于定义类型变量,这类似于其他语言中的泛型参数。
from typing import TypeVar, List
# 定义一个类型变量 T
T = TypeVar('T')
def get_first_element(lst: List[T]) -> T:
return lst[0]
# 使用泛型函数
numbers = [1, 2, 3]
first_number = get_first_element(numbers) # 类型为 int
strings = ["a", "b", "c"]
first_string = get_first_element(strings) # 类型为 str
T 是一个类型变量,List[T]
表示一个列表,其中的元素类型为 T。get_first_element 函数可以处理任何类型的列表,并返回与列表元素相同类型的值。
泛型在类中使用
创建
Python 的类也可以使用泛型。通过 TypeVar 和 Generic,可以定义泛型类。
from typing import TypeVar, Generic, List
T = TypeVar('T')
class Stack(Generic[T]):
def __init__(self) -> None:
self.items: List[T] = []
def push(self, item: T) -> None:
self.items.append(item)
def pop(self) -> T:
return self.items.pop()
def __repr__(self) -> str:
return f"Stack({self.items})"
# 使用泛型类
stack = Stack[int]()
stack.push(1)
stack.push(2)
print(stack) # 输出: Stack([1, 2])
print(stack.pop()) # 输出: 2
Stack 类是一个泛型类,T 是它的类型参数。通过指定 Stack[int]
,可以明确地表示这是一个存储整数的栈。
继承泛型类
继承泛型类时,可以指定具体的类型,也可以继续使用类型变量。
class Box(Generic[T]):
def __init__(self, content: T):
self.content = content
class SpecialBox(Box[int]):
def __init__(self, content: int):
super().__init__(content)
SpecialBox 继承了 Box 类,并指定其类型为int。
类型约束
在定义类型变量时,可以指定类型约束,限制类型变量的取值范围。
from typing import TypeVar, Generic, List
T = TypeVar('T', int, float) # T 只能是 int 或 float
def add(a: T, b: T) -> T:
return a + b
# 使用泛型函数
print(add(1, 2)) # 输出: 3
print(add(1.5, 2.5)) # 输出: 4.0
# print(add("a", "b")) # 会报错,因为 "a" 和 "b" 不是 int 或 float
T 被限制为只能是 int 或 float,这使得 add 函数只能处理这两种类型的参数。
高级泛型
协变和逆变
协变(Covariance)
协变是指在泛型类型中,子类的泛型类型可以替代父类的泛型类型。换句话说,如果 B 是 A 的子类,那么 List[B] 可以被视为 List[A] 的子类型。
原理:
- 协变允许子类的实例在需要父类实例的地方使用。
- 在函数返回值中,协变是安全的,因为返回值的类型是子类,可以被视为父类。
逆变(Contravariance)
逆变是指在泛型类型中,父类的泛型类型可以替代子类的泛型类型。 换句话说,如果 B 是 A 的子类,那么 List[A] 可以被视为 List[B] 的子类型。
原理:
- 逆变允许父类的实例在需要子类实例的地方使用。
- 在函数参数中,逆变是安全的,因为函数可以接受父类的实例,而实际调用时可能会传入子类的实例。
一个简单的事件处理系统,其中协变用于事件处理器的返回值,逆变用于事件发布者的参数。
from typing import TypeVar, Generic, Protocol, Callable, Any
# 定义 Event 类和它的子类
class Event:
def __init__(self, name: str):
self.name = name
class MouseEvent(Event):
def __init__(self, name: str, x: int, y: int):
super().__init__(name)
self.x = x
self.y = y
class KeyEvent(Event):
def __init__(self, name: str, key: str):
super().__init__(name)
self.key = key
# 定义协变的类型变量
T_co = TypeVar('T_co', covariant=True)
# 定义逆变的类型变量
T_contra = TypeVar('T_contra', contravariant=True)
# 定义事件处理器协议(协变)
class EventHandler(Protocol[T_co]):
def handle(self, event: T_co) -> None:
...
# 定义事件发布者类(逆变)
class EventPublisher(Generic[T_contra]):
def __init__(self):
self.handlers: list[Callable[[T_contra], None]] = []
def subscribe(self, handler: Callable[[T_contra], None]) -> None:
self.handlers.append(handler)
def publish(self, event: T_contra) -> None:
for handler in self.handlers:
handler(event)
# 定义具体的事件处理器
class MouseEventPrinter:
def handle(self, event: MouseEvent) -> None:
print(f"MouseEvent: {event.name} at ({event.x}, {event.y})")
class KeyEventPrinter:
def handle(self, event: KeyEvent) -> None:
print(f"KeyEvent: {event.name} with key '{event.key}'")
# 定义一个通用的事件处理器
class EventPrinter:
def handle(self, event: Event) -> None:
print(f"Event: {event.name}")
# 使用协变和逆变
# 创建一个事件发布者,类型为 Event
event_publisher = EventPublisher[Event]()
# 创建一个 MouseEvent 处理器
mouse_event_printer = MouseEventPrinter()
# 创建一个 KeyEvent 处理器
key_event_printer = KeyEventPrinter()
# 创建一个通用的事件处理器
event_printer = EventPrinter()
# 由于逆变,可以将 MouseEventPrinter 和 KeyEventPrinter 的 handle 方法作为回调函数
event_publisher.subscribe(mouse_event_printer.handle)
event_publisher.subscribe(key_event_printer.handle)
event_publisher.subscribe(event_printer.handle)
# 发布一个 MouseEvent
event_publisher.publish(MouseEvent("Click", 100, 200))
# 发布一个 KeyEvent
event_publisher.publish(KeyEvent("Press", "Enter"))
有界泛型
可以对类型变量指定约束,限制可以与泛型类型一起使用的类型。
from typing import TypeVar, Iterable
T = TypeVar('T', bound=Iterable)
def sum_items(items: list[T]) -> T:
return sum(items)
泛型协议
可以使用泛型协议来定义可与泛型类型一起使用的接口。
from typing import Protocol, TypeVar
T = TypeVar('T')
class Drawable(Protocol[T]):
def draw(self, item: T) -> None:
...
def draw_all(items: list[Drawable[T]]) -> None:
for item in items:
item.draw(item)
Drawable 协议定义了一个需要 draw 方法的泛型接口。
泛型的应用
泛型在实际项目中非常有用,可以结合使用泛型和多态来实现更加灵活和可扩展的设计。
项目管理系统
from typing import TypeVar, Generic, List
# 定义一个类型变量 T,限制为 Task 的子类
T = TypeVar('T', bound='Task') # 使用 bound 确保 T 是 Task 或其子类
# 定义任务基类
class Task:
def execute(self) -> None:
pass # 基类中的 execute 方法什么也不做,具体实现由子类提供
# 定义 CodingTask 类,继承自 Task
class CodingTask(Task):
def execute(self) -> None:
print("Writing code...") # 重写 execute 方法,实现具体的编码任务逻辑
# 定义 TestingTask 类,继承自 Task
class TestingTask(Task):
def execute(self) -> None:
print("Running tests...") # 重写 execute 方法,实现具体的测试任务逻辑
# 定义 Project 泛型类,使用类型变量 T
class Project(Generic[T]):
def __init__(self, name: str):
self.name = name # 项目名称
self.tasks: List[T] = [] # 项目中的任务列表,类型为 T
def add_task(self, task: T) -> None:
self.tasks.append(task) # 向项目中添加一个任务
def get_tasks(self) -> List[T]:
return self.tasks # 返回项目中的任务列表
# 创建一个 CodingTask 项目的实例
coding_project = Project[CodingTask]("Coding Project")
# 创建一个 TestingTask 项目的实例
testing_project = Project[TestingTask]("Testing Project")
# 向 coding_project 添加一个 CodingTask 任务
coding_project.add_task(CodingTask())
# 向 testing_project 添加一个 TestingTask 任务
testing_project.add_task(TestingTask())
# 遍历 coding_project 中的任务列表,调用每个任务的 execute 方法
for task in coding_project.get_tasks():
task.execute() # 输出: Writing code...
# 遍历 testing_project 中的任务列表,调用每个任务的 execute 方法
for task in testing_project.get_tasks():
task.execute() # 输出: Running tests...
通用数据处理器
from typing import TypeVar, Generic, Optional
T = TypeVar('T')
class DataProcessor(Generic[T]):
def __init__(self, data: T) -> None:
self.data = data
def process(self) -> Optional[T]:
if isinstance(self.data, (int, float)):
return self.data * 2
elif isinstance(self.data, str):
return self.data.upper()
return None
# 使用示例
processor_int = DataProcessor(10)
print(processor_int.process()) # 20
processor_str = DataProcessor("hello")
print(processor_str.process()) # "HELLO"
框架中的泛型
Pydantic 中的泛型模型
Pydantic 的 BaseModel 通过泛型支持动态数据类型验证,常用于数据模型定义和序列化。
from typing import Generic, TypeVar, List
from pydantic import BaseModel
T = TypeVar('T')
# 定义泛型响应模型
class ResponseModel(BaseModel, Generic[T]):
code: int
data: T
message: str
# 具体类型实例化
user_data = {"name": "Alice", "age": 30}
response = ResponseModel[dict](code=200, data=user_data, message="Success")
print(response.model_dump()) # 输出序列化数据
ResponseModel 继承 Generic[T],允许通过 T 动态指定 data 字段的类型。 实例化时明确 T 为 dict,实现类型安全的字段验证。
FastAPI 中的泛型路由响应
FastAPI 利用泛型统一接口响应格式,提升 API 文档的自动生成能力。
from fastapi import APIRouter
from typing import TypeVar, Generic
router = APIRouter()
T = TypeVar('T')
class PaginatedResponse(Generic[T]):
def __init__(self, items: List[T], total: int):
self.items = items
self.total = total
@router.get("/users")
async def get_users() -> PaginatedResponse[dict]:
users = [{"id": 1, "name": "Bob"}, {"id": 2, "name": "Charlie"}]
return PaginatedResponse(items=users, total=2)
PaginatedResponse 是泛型类,通过 T 指定分页数据的具体类型(如 dict 或自定义模型)。 路由返回值标注为 PaginatedResponse[dict],Swagger 文档会自动展示数据结构。
SQLModel 泛型ORM
SQLModel 结合 SQLAlchemy 和 Pydantic,通过泛型简化数据库模型定义。
from sqlmodel import SQLModel, Field, Generic, TypeVar
T = TypeVar('T', bound=SQLModel)
class BaseDAO(Generic[T]):
def __init__(self, model: type[T]):
self.model = model
def create(self, data: T) -> T:
return data # 实际应执行数据库插入操作
# 定义用户模型
class User(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
# 使用泛型 DAO 操作
user_dao = BaseDAO[User](User)
new_user = User(name="David")
created_user = user_dao.create(new_user)
BaseDAO 泛型类通过 T 绑定到 SQLModel 的子类,实现通用的 CRUD 操作。 User 模型继承 SQLModel,泛型 DAO 可复用于其他模型。
Typer 中的泛型命令行参数
Typer 框架通过泛型实现类型安全的 CLI 参数解析。
import typer
from typing import List, TypeVar
T = TypeVar('T')
def print_items(items: List[T]) -> None:
for item in items:
typer.echo(f"Item: {item}")
if __name__ == "__main__":
typer.run(print_items)
函数 print_items 使用 List[T] 泛型标注参数,支持任意类型的列表输入。 Typer 根据类型提示自动解析命令行参数并转换类型。