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 可以在使用中逐步熟悉。

上次编辑于:
贡献者: 15327360835
Loading...