10 typing类型注解
Python是动态类型语言,变量不需要声明类型。这很灵活,但代码一大就容易出问题——函数参数传错了类型、返回值类型和预期不符,这些错误只能在运行时发现。
typing模块给Python加上了类型注解(Type Hints),让IDE能提前发现类型错误,代码也更容易理解。类型注解不会影响运行时行为,只是给开发者和工具看的。
一、基本类型注解
1.1 变量注解
python
# 变量类型注解
name: str = "大志"
age: int = 28
score: float = 95.5
active: bool = True1.2 函数参数和返回值
python
# 函数类型注解
def greet(name: str) -> str:
return f"你好, {name}!"
def add(a: int, b: int) -> int:
return a + b
# 无返回值用None
def log(message: str) -> None:
print(message)1.3 容器类型
python
# 列表
scores: list[int] = [90, 85, 92]
# 字典
user: dict[str, str] = {"name": "大志", "email": "test@example.com"}
# 集合
tags: set[str] = {"python", "ai"}
# 元组
point: tuple[int, int] = (10, 20)Python 3.9+可以直接用内置类型(如list[int]),不需要从typing导入List。
二、typing模块的核心类型
2.1 Any
任意类型,不做类型检查。
python
from typing import Any
def process(data: Any) -> None:
# data可以是任何类型
print(data)尽量少用Any,它等于关闭了类型检查。
2.2 Union
联合类型,表示可以是多种类型之一。
python
from typing import Union
# Python 3.10+ 可以用 | 语法
def process(value: int | str) -> str:
return str(value)
# 旧写法
def process(value: Union[int, str]) -> str:
return str(value)2.3 Optional
可选类型,表示可以是指定类型或None。
python
from typing import Optional
# Optional[str] 等价于 Union[str, None]
def find_user(user_id: int) -> Optional[str]:
if user_id == 1:
return "大志"
return None
# Python 3.10+ 写法
def find_user(user_id: int) -> str | None:
...2.4 Callable
可调用对象类型(函数、方法等)。
python
from typing import Callable
# Callable[[参数类型], 返回类型]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
def add(a: int, b: int) -> int:
return a + b
apply(add, 1, 2) # 3
# 任意参数
def run(func: Callable[..., None]) -> None:
func()2.5 Literal
字面量类型,限定值只能是几个固定的选项。
python
from typing import Literal
def set_mode(mode: Literal["fast", "slow", "auto"]) -> None:
print(f"模式: {mode}")
set_mode("fast") # OK
set_mode("medium") # 类型检查器会报错2.6 TypeVar
类型变量,用于泛型。
python
from typing import TypeVar
T = TypeVar('T')
def first(items: list[T]) -> T:
return items[0]
# 返回类型会根据输入自动推断
result = first([1, 2, 3]) # result的类型是int
result = first(["a", "b"]) # result的类型是str2.7 Generic
泛型基类。
python
from typing import Generic, TypeVar
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] = Stack()
int_stack.push(1)
int_stack.push(2)
str_stack: Stack[str] = Stack()
str_stack.push("hello")三、Python 3.10+的新语法
3.1 联合类型用 |
python
# 旧写法
from typing import Union
def process(value: Union[int, str]) -> None: ...
# 新写法(3.10+)
def process(value: int | str) -> None: ...3.2 类型别名用type
python
# 旧写法
from typing import TypeAlias
Vector: TypeAlias = list[float]
# 新写法(3.12+)
type Vector = list[float]
type UserID = int3.3 TypeVar简写
python
# 旧写法
from typing import TypeVar
T = TypeVar('T')
def first(items: list[T]) -> T: ...
# 新写法(3.12+)
def first[T](items: list[T]) -> T: ...四、高级类型
4.1 TypedDict
带类型键的字典。
python
from typing import TypedDict
class UserDict(TypedDict):
name: str
age: int
email: str
# 使用
user: UserDict = {
"name": "大志",
"age": 28,
"email": "test@example.com"
}
# 缺少键或类型错误时,类型检查器会报错4.2 Protocol
结构化子类型(鸭子类型的类型注解版本)。
python
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
class Circle:
def draw(self) -> None:
print("画圆")
class Square:
def draw(self) -> None:
print("画方")
# Circle和Square都没有继承Drawable,但都有draw方法
def render(shape: Drawable) -> None:
shape.draw()
render(Circle()) # OK
render(Square()) # OK4.3 Final
标记不可重新赋值的常量。
python
from typing import Final
MAX_RETRIES: Final = 3
API_URL: Final = "https://api.example.com"
# 重新赋值时类型检查器会报错
MAX_RETRIES = 5 # 类型检查器报错4.4 ClassVar
标记类变量(不参与实例的类型检查)。
python
from typing import ClassVar
class Agent:
version: ClassVar[str] = "1.0"
name: str
def __init__(self, name: str) -> None:
self.name = name
# version是类变量,所有实例共享
# name是实例变量,每个实例不同4.5 Annotated
给类型添加额外元数据。
python
from typing import Annotated
# Annotated[类型, 元数据]
UserId = Annotated[int, "用户ID"]
Email = Annotated[str, "邮箱地址"]
def get_user(user_id: UserId) -> str:
return "大志"
# 常用于FastAPI等框架的参数验证
# from fastapi import Query
# def search(q: Annotated[str, Query(min_length=1)]): ...五、实际项目中的用法
5.1 API返回类型
python
from typing import TypedDict
class ApiResponse(TypedDict):
code: int
message: str
data: dict | None
def fetch_user(user_id: int) -> ApiResponse:
return {
"code": 200,
"message": "success",
"data": {"name": "大志"}
}5.2 配置类型
python
from typing import TypedDict, Literal
class Config(TypedDict):
host: str
port: int
debug: bool
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"]
def load_config() -> Config:
return {
"host": "localhost",
"port": 8080,
"debug": False,
"log_level": "INFO"
}5.3 回调函数类型
python
from typing import Callable
EventHandler = Callable[[str, dict], None]
def on_event(handler: EventHandler) -> None:
handler("click", {"x": 100, "y": 200})
def my_handler(event: str, data: dict) -> None:
print(f"事件: {event}, 数据: {data}")
on_event(my_handler)5.4 泛型容器
python
from typing import Generic, TypeVar, Iterator
T = TypeVar('T')
class Repository(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def add(self, item: T) -> None:
self._items.append(item)
def get_all(self) -> list[T]:
return self._items.copy()
def find(self, predicate: Callable[[T], bool]) -> T | None:
for item in self._items:
if predicate(item):
return item
return None
# 使用
user_repo: Repository[User] = Repository()
user_repo.add(User("大志"))六、运行时类型检查
类型注解默认不影响运行时,但可以用get_type_hints()获取:
python
from typing import get_type_hints
def greet(name: str) -> str:
return f"你好, {name}!"
hints = get_type_hints(greet)
print(hints) # {'name': <class 'str'>, 'return': <class 'str'>}如果需要运行时检查,可以用typeguard库:
python
# pip install typeguard
from typeguard import typechecked
@typechecked
def add(a: int, b: int) -> int:
return a + b
add(1, 2) # OK
add("1", 2) # TypeError七、总结
typing模块的核心类型:
| 类型 | 用途 |
|---|---|
Any | 任意类型 |
Union[X, Y] 或 X | Y | 联合类型 |
Optional[X] 或 X | None | 可选类型 |
Callable[[Args], Ret] | 可调用对象 |
Literal[...] | 字面量类型 |
TypeVar | 类型变量(泛型) |
Generic | 泛型基类 |
TypedDict | 带类型的字典 |
Protocol | 结构化子类型 |
Final | 不可重新赋值 |
ClassVar | 类变量 |
使用建议:
- 函数签名必加:参数和返回值加上类型注解,IDE能提供更好的补全和检查
- 变量类型按需:复杂类型加注解,简单类型可以省略
- 用新语法:Python 3.10+用
X | Y代替Union[X, Y],3.12+用type定义类型别名 - 不要过度使用:简单的代码不需要复杂的泛型,保持简洁
类型注解是Python从"能跑就行"走向"工程化"的重要一步。项目大了、团队协作时,类型注解的价值就体现出来了。