Python类型注解
大约 5 分钟
类型注解(typing)
类型注解(Type Hints)给变量、函数参数、返回值标明类型。Python 运行时并不强制检查注解,但 IDE(Pylance)和 mypy 等工具会在编写阶段提前发现类型错误。现代 Python 项目(FastAPI、Pydantic)的参数校验、自动文档全部建立在类型注解之上,属于写项目的前置必备知识。
基础注解
# 变量注解:变量名后跟冒号和类型
name: str = "时游"
age: int = 28
scores: list = [90, 85] # 注解错了也不会报错,注解只是提示,不改变运行结果
# 函数注解:参数用冒号,返回值用 -> 箭头
def greet(name: str, times: int = 1) -> str:
return f"hello {name} " * times
print(greet("时游", 2)) # hello 时游 hello 时游
# 注解存放在 __annotations__ 属性中,运行时可以读取
print(greet.__annotations__) # {'name': <class 'str'>, 'times': <class 'int'>, 'return': <class 'str'>}
容器类型
Python 3.9+ 可以直接用内置容器标注元素类型;3.8 及以前需要从 typing 导入 List、Dict 等大写版本。
# 容器内元素的类型:list[元素类型]
names: list[str] = ["Tom", "Jerry"]
# dict[键类型, 值类型]
ages: dict[str, int] = {"Tom": 18}
# tuple:固定长度时逐个标注;任意长度用 ...
point: tuple[int, int] = (3, 4)
points: tuple[int, ...] = (1, 2, 3, 4)
# set 与 frozenset
tags: set[str] = {"python", "爬虫"}
Optional、Union 与 | 语法
from typing import Optional, Union
# Optional[str] 等价于 Union[str, None]:值可以是str,也可以是None
def find_user(uid: int) -> Optional[str]:
users = {1: "Tom"}
return users.get(uid) # 找不到时返回None
print(find_user(1)) # Tom
print(find_user(2)) # None
# Union:多个类型之一
def parse(value: Union[int, str]) -> int:
return int(value)
# Python 3.10+ 推荐用 | 写法,更简洁
def find_user2(uid: int) -> str | None:
return {1: "Tom"}.get(uid)
def parse2(value: int | str) -> int:
return int(value)
Literal、Any 与 Callable
from typing import Literal, Any, Callable
# Literal:限定只能是指定的几个字面量值,常用于配置项、排序方向
def set_order(order: Literal["asc", "desc"] = "asc") -> None:
print(f"排序方向:{order}")
set_order("desc") # 排序方向:desc
# set_order("xxx") # IDE直接标红提示:字面量类型不匹配
# Any:任意类型,等于放弃检查。少用,通常出现在过渡旧代码时
data: Any = "任意值"
# Callable:函数类型,[参数类型列表] -> 返回类型
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
print(apply(lambda x, y: x + y, 1, 2)) # 3
TypedDict 与 NamedTuple
描述「字典结构」和「带字段名的元组」,适合轻量数据建模。
from typing import TypedDict, NamedTuple
# TypedDict:给字典的键和值规定类型,比普通dict[str, int]表达力更强
class UserDict(TypedDict):
uid: int
name: str
vip: bool
user: UserDict = {"uid": 1, "name": "Tom", "vip": True}
print(user["name"]) # Tom
# user = {"uid": 1} # IDE提示:缺少 name、vip 字段
# NamedTuple:可以通过字段名访问的元组
class Point(NamedTuple):
x: int
y: int
p = Point(3, 4)
print(p.x, p.y) # 3 4
print(p[0], p[1]) # 3 4,仍然是元组,可以索引
dataclass:数据类
dataclass 装饰器自动生成
__init__、__repr__、__eq__等方法,是现代 Python 组织数据的标准写法,也是理解 Pydantic 模型的基础。
from dataclasses import dataclass, field
@dataclass
class Article:
title: str
author: str
# field(default_factory=...):可变默认值必须用工厂函数,避免所有实例共享同一个列表(类似类属性陷阱)
tags: list[str] = field(default_factory=list)
views: int = 0
def summary(self) -> str: # 也可以定义普通方法
return f"《{self.title}》by {self.author}"
a1 = Article("Python笔记", "时游", tags=["python"])
a2 = Article("Python笔记", "时游", tags=["python"])
print(a1.summary()) # 《Python笔记》by 时游
print(a1) # Article(title='Python笔记', author='时游', tags=['python'], views=0)
print(a1 == a2) # True,自动生成了按值比较的__eq__
print(a1 is a2) # False,仍然是两个不同对象
泛型函数
from typing import TypeVar
# TypeVar:类型变量,让「进什么类型、出什么类型」保持一致
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
print(first([1, 2, 3])) # 1,此时T被推断为int
print(first(["a", "b"])) # a,此时T被推断为str
# K, V两个类型变量:交换字典的键值
K = TypeVar("K")
V = TypeVar("V")
def invert(d: dict[K, V]) -> dict[V, K]:
return {v: k for k, v in d.items()}
print(invert({"a": 1, "b": 2})) # {1: 'a', 2: 'b'}
类型别名
from typing import TypeAlias
# 复杂类型太长时起个别名,语义更清晰
UserId: TypeAlias = int
UserTable: TypeAlias = dict[UserId, str]
users: UserTable = {1: "Tom", 2: "Jerry"}
print(users[1]) # Tom
# Python 3.12+ 还可以用 type 语句:type UserTable = dict[int, str]
静态类型检查
运行时不报错不等于类型正确。实际项目中用工具做静态检查:
# 方式1:VS Code 安装 Python 插件(Pylance)即自带实时检查,错误直接标红
# 方式2:命令行批量检查(可接入 CI)
pip install mypy
mypy main.py
# 常用参数
mypy --strict main.py # 严格模式,检查更细致
与 FastAPI/Pydantic 的衔接
类型注解是 FastAPI 的核心机制:请求参数自动校验、API 文档自动生成都靠它。同样的函数签名,在普通 Python 里只是「提示」,在 FastAPI 里变成「运行时强制约束」。
# pip install fastapi uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# Pydantic模型:基于类型注解的运行时数据校验(下一阶段Web开发笔记会展开)
class CreateUserRequest(BaseModel):
name: str
age: int
@app.post("/users")
async def create_user(req: CreateUserRequest) -> dict:
# age传"abc"时FastAPI自动返回422错误,不需要手写校验
return {"id": 1, "name": req.name}
# 启动:uvicorn main:app --reload
| 场景 | 普通Python中 | FastAPI/Pydantic中 |
|---|---|---|
def f(x: int) 传入 "abc" | 不报错,注解只是提示 | 自动校验并返回 422 |
-> str 返回值 | 不检查 | 用于生成 API 文档的响应类型 |
| 类型注解的作用 | IDE 提示、mypy 静态检查 | 变成运行时的校验规则 |
学习路线建议:先掌握本篇的变量/函数/容器/Optional 注解与 dataclass,再去写 FastAPI 会非常自然;TypedDict、泛型、TypeAlias 可以在使用中逐步熟悉。
Loading...
