Python 类型系统 typing 模块详解
1. 模块概述
typing
模块在 Python 3.5 中引入,用于支持类型提示(Type Hints)。它提供了:
- 用于类型注释的工具
- 泛型类型支持
- 类型别名
- 回调协议
- 以及其他高级类型系统特性
2. 基础类型提示
2.1 基本类型注释
from typing import List, Dict, Set, Tuple, Optional
# 变量类型注释
name: str = "Alice"
age: int = 30
is_student: bool = False
# 函数参数和返回值类型注释
def greet(name: str) -> str:
return f"Hello, {name}"
# 容器类型
numbers: List[int] = [1, 2, 3]
person: Dict[str, str] = {"name": "Alice", "email": "alice@example.com"}
unique_numbers: Set[int] = {1, 2, 3}
coordinates: Tuple[float, float] = (10.5, 20.3)
# 可选类型
maybe_name: Optional[str] = None # 等同于 Union[str, None]
2.2 类型别名
from typing import List, Tuple
# 创建类型别名
Vector = List[float]
Point = Tuple[float, float]
def scale_vector(v: Vector, factor: float) -> Vector:
return [x * factor for x in v]
def distance(p1: Point, p2: Point) -> float:
return ((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)**0.5
3. 复合类型
3.1 Union 类型
- 表示属于Union中的任意一种类型均合法
from typing import Union
def process_value(value: Union[int, str]) -> None:
if isinstance(value, int):
print(f"Processing integer: {value}")
else:
print(f"Processing string: {value}")
process_value(10) # Processing integer: 10
process_value("hi") # Processing string: hi
3.2 Optional 类型
- Optional[str] = Union[str, None]
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)
print(find_user(1)) # Alice
print(find_user(3)) # None
3.3 Any 类型
- 表示可以使用任何类型,不建议常用
from typing import Any
def process_any(value: Any) -> Any:
print(f"Processing {value}")
return value
result = process_any(10) # Processing 10
result = process_any("text") # Processing text
4. 泛型类型
4.1 TypeVar
from typing import TypeVar, List, Sequence
T = TypeVar('T') # 任意类型
Num = TypeVar('Num', int, float) # 仅限于int和float
def first_element(items: Sequence[T]) -> T:
return items[0]
print(first_element([1, 2, 3])) # 1
print(first_element(["a", "b"])) # a
4.2 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()
int_stack = Stack[int]()
int_stack.push(1)
int_stack.push(2)
print(int_stack.pop()) # 2
5. 函数类型
5.1 Callable
from typing import Callable
def apply_func(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
def add(x: int, y: int) -> int:
return x + y
print(apply_func(add, 3, 5)) # 8
5.2 可调用对象协议
from typing import Protocol
class Adder(Protocol):
def __call__(self, a: int, b: int) -> int:
...
def apply_adder(adder: Adder, x: int, y: int) -> int:
return adder(x, y)
print(apply_adder(lambda a, b: a + b, 10, 20)) # 30
6. 带元数据的类型Annotated
Annotated
是 Python typing
模块中一个强大但常被忽视的类型注解工具,它允许我们在类型提示中添加额外的元数据。这个功能在 Python 3.9 中引入,为类型系统提供了更大的灵活性。Annotated
的基本形式如下:
from typing import Annotated
Annotated[<type>, <metadata1>, <metadata2>, ...]
其中:
<type>
是基础类型<metadata>
可以是任意对象,提供额外的类型信息
6.1 基本示例
from typing import Annotated
# 给int类型添加单位信息
Distance = Annotated[int, "meters"]
Temperature = Annotated[float, "celsius"]
def get_distance() -> Distance:
return 100
def get_temperature() -> Temperature:
return 25.5
6.2 核心特性
- 保留类型信息
Annotated
不会改变原始类型,只是附加元数据:
from typing import Annotated, get_type_hints
UserId = Annotated[int, "user identifier"]
def get_user(id: UserId) -> str:
return f"user_{id}"
# 获取类型提示
hints = get_type_hints(get_user)
print(hints) # {'id': typing.Annotated[int, 'user identifier'], 'return': <class 'str'>}
- 多重元数据
可以附加多个元数据项:
from typing import Annotated
# 带有范围和单位的温度类型
BoundedTemp = Annotated[float, "celsius", (0.0, 100.0)]
def check_temp(temp: BoundedTemp) -> bool:
return 0.0 <= temp <= 100.0
6.3 应用场景
- 数据验证
结合 Pydantic 等库进行数据验证:
from typing import Annotated
from pydantic import BaseModel, Field
PositiveInt = Annotated[int, Field(gt=0)]
class User(BaseModel):
id: PositiveInt
name: str
# 有效数据
user = User(id=1, name="Alice")
# 无效数据会引发验证错误
# user = User(id=-1, name="Bob") # 抛出ValidationError
- 参数约束
在 FastAPI 等框架中指定参数约束:
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[str, Query(min_length=3, max_length=50)] = "default"
):
return {"q": q}
- 文档增强
为类型添加文档信息:
from typing import Annotated
from typing_extensions import Doc # Python 3.11+
DatabaseConnection = Annotated[
str,
Doc("A connection string in the format 'user:password@host:port/database'"),
Doc("Example: 'admin:secret@localhost:5432/mydb'")
]
def connect_db(conn_str: DatabaseConnection) -> None:
"""Connect to the database."""
print(f"Connecting with: {conn_str}")
6.4 与其他类型工具结合
- 与 NewType 结合
from typing import Annotated, NewType
UserId = NewType('UserId', int)
AnnotatedUserId = Annotated[UserId, "primary key"]
def get_user_name(user_id: AnnotatedUserId) -> str:
return f"user_{user_id}"
print(get_user_name(UserId(42))) # user_42
- 与 Literal 结合
from typing import Annotated, Literal
HttpMethod = Literal["GET", "POST", "PUT", "DELETE"]
AnnotatedHttpMethod = Annotated[HttpMethod, "HTTP method"]
def log_request(method: AnnotatedHttpMethod) -> None:
print(f"Received {method} request")
log_request("GET") # 有效
# log_request("HEAD") # 类型检查器会报错
6.5 运行时访问元数据
from typing import Annotated, get_type_hints
def extract_metadata(annotated_type):
origin = get_origin(annotated_type)
if origin is not Annotated:
return None
return get_args(annotated_type)[1:] # 返回元数据部分
# 定义带注解的类型
Count = Annotated[int, "counter", "must be positive"]
hints = get_type_hints(lambda x: x, localns={'x': Count})
metadata = extract_metadata(hints['x'])
print(metadata) # ('counter', 'must be positive')
6.6. 实际案例:数据库字段类型
from typing import Annotated, Optional
from datetime import datetime
# 定义带约束的字段类型
Username = Annotated[str, "username", "max_length=32", "alphanumeric"]
Email = Annotated[str, "email", "max_length=255"]
CreatedAt = Annotated[datetime, "auto_now_add=True"]
UpdatedAt = Annotated[Optional[datetime], "auto_now=True", "nullable=True"]
class UserProfile:
def __init__(
self,
username: Username,
email: Email,
created_at: CreatedAt,
updated_at: UpdatedAt = None
):
self.username = username
self.email = email
self.created_at = created_at
self.updated_at = updated_at
# 这些注解可以被ORM框架或序列化库读取并使用
Annotated
为 Python 的类型系统提供了强大的扩展能力,使得类型提示不仅可以用于静态检查,还能携带丰富的运行时信息,为框架开发和复杂系统设计提供了更多可能性。
7. 高级类型特性
7.1 Literal 类型
from typing import Literal
def draw_shape(shape: Literal["circle", "square", "triangle"]) -> None:
print(f"Drawing a {shape}")
draw_shape("circle") # 正确
draw_shape("square") # 正确
# draw_shape("rectangle") # 类型检查器会报错
7.2 TypedDict
from typing import TypedDict, Optional
class Person(TypedDict):
name: str
age: int
email: Optional[str]
alice: Person = {"name": "Alice", "age": 30}
bob: Person = {"name": "Bob", "age": 25, "email": "bob@example.com"}
7.3 NewType
from typing import NewType
UserId = NewType('UserId', int)
admin_id = UserId(1)
def get_user_name(user_id: UserId) -> str:
return f"user_{user_id}"
print(get_user_name(admin_id)) # 正确
# print(get_user_name(12345)) # 类型检查器会报错
8. 运行时类型检查
8.1 typeguard
虽然 typing
模块主要用于静态类型检查,但可以与第三方库如 typeguard
结合实现运行时检查:
from typeguard import typechecked
from typing import List
@typechecked
def process_numbers(numbers: List[int]) -> float:
return sum(numbers) / len(numbers)
print(process_numbers([1, 2, 3])) # 2.0
# process_numbers([1, '2', 3]) # 运行时抛出TypeError
8.2 get_type_hints
from typing import get_type_hints, List, Dict
def example(a: int, b: str = "default") -> Dict[str, List[int]]:
return {b: [a]}
print(get_type_hints(example))
# 输出: {'a': <class 'int'>, 'b': <class 'str'>, 'return': Dict[str, List[int]]}
9. Python 3.10+ 新特性
9.1 联合类型语法糖
# Python 3.10 之前
from typing import Union
def old_way(x: Union[int, str]) -> Union[int, str]:
return x
# Python 3.10+
def new_way(x: int | str) -> int | str:
return x
9.2 TypeGuard
from typing import TypeGuard, List, Union
def is_str_list(val: List[Union[str, int]]) -> TypeGuard[List[str]]:
return all(isinstance(x, str) for x in val)
def process_items(items: List[Union[str, int]]) -> None:
if is_str_list(items):
print("All strings:", [s.upper() for s in items])
else:
print("Mixed types:", items)
process_items(["a", "b", "c"]) # All strings: ['A', 'B', 'C']
process_items([1, "b", 3]) # Mixed types: [1, 'b', 3]
10. 迁移策略
10.1 逐步添加类型提示
# 第一阶段:无类型提示
def old_function(x):
return x * 2
# 第二阶段:添加简单类型提示
def partially_typed_function(x: int) -> int:
return x * 2
# 第三阶段:完整类型提示
from typing import TypeVar, Sequence
T = TypeVar('T')
def fully_typed_function(items: Sequence[T], multiplier: int) -> list[T]:
return [item * multiplier for item in items]
10.2 处理动态类型代码
import types
from typing import Any, Union, cast
def dynamic_function(func: Union[types.FunctionType, types.BuiltinFunctionType]) -> Any:
result = func()
# 如果我们知道特定函数的返回类型,可以使用cast
if func.__name__ == 'get_answer':
return cast(int, result)
return result
typing 模块总结
- 为 Python 添加静态类型提示支持
- 提供丰富的类型注解工具(
List
,Dict
,Union
等) - 支持泛型编程(
TypeVar
,Generic
) - 包含高级类型特性(
Literal
,TypedDict
,Protocol
等) - 与 Python 3.10+ 的新语法(
|
运算符)良好集成 - 类型提示在运行时几乎没有性能影响,因为它们主要被静态类型检查器使用
typing
模块中的一些特殊形式(如Generic
)可能会引入轻微的开销- 在性能关键代码中,考虑使用简单的类型提示或仅在开发时使用类型检查