首页
/ Python 类型注解(Type Hints)入门:驱动 FastAPI 声明式 API 的基石

Python 类型注解(Type Hints)入门:驱动 FastAPI 声明式 API 的基石

2026-09-07 23:39:08作者:董斯意

Python 3 提供了可选的 type hints(类型注解) 语法,允许开发者在变量、函数参数与返回值上声明类型。本文档源自 FastAPI 官方文档中文翻译(docs/hi/docs/python-types.md,仓库内同内容文档覆盖多语言目录),它是学习 FastAPI 之前最重要的一篇基础指南——因为 FastAPI 完全构建在这些 type hints 之上:框架正是靠你声明的类型来完成请求数据解析、校验、转换、自动错误生成与 OpenAPI 文档生成。读完本文,你将掌握 Python 类型注解的核心语法(简单类型、泛型容器、Union、可空类型、类与 Pydantic 模型、Annotated 元数据),并理解它们如何在 FastAPI 的源码层面被转换为真实的 API 能力。

如果你已经是 Python 类型注解专家,可以直接跳过本页进入 Tutorial - User Guide(仓库中为 docs/hi/docs/tutorial/index.md)。

动机:一个没有类型注解的函数

先从最朴素的例子看起,这也是官方入门页的第一个示例,源码见 docs_src/python_types/tutorial001_py310.py

def get_full_name(first_name, last_name):
    full_name = first_name.title() + " " + last_name.title()
    return full_name


print(get_full_name("john", "doe"))

调用这个程序输出:

John Doe

函数做的事情很简单:

  • 接收 first_namelast_name 两个参数;
  • title() 把每个参数的首字母转为大写;
  • 用中间的一个空格把它们拼接(concatenate)成一个字符串返回。

亲手编辑一下:糟糕的自动补全体验

这是一个非常简单的程序,但假设你是从零开始写它的:写到一半时,你需要在某个参数上调用"把首字母大写的方法"——那到底叫 upperuppercasefirst_uppercase?还是 capitalize

你于是求助程序员的老朋友——编辑器的自动补全:敲下函数第一个参数 first_name,输入一个点号 .,再按 Ctrl+Space 触发补全。可惜的是,编辑器一无所知,弹出的列表里什么有用的东西都没有:

没有任何类型注解时,编辑器无法针对 first_name 给出任何自动补全选项

问题的根源在于:Python 是动态类型语言,first_name 此刻可以是任何东西——字符串、数字、对象,甚至一个函数。编辑器没有任何信息可以推断它的方法集。

添加类型注解(Type Hints)

现在只改上一版代码的一行:把函数参数列表从:

    first_name, last_name

改成:

    first_name: str, last_name: str

仅此而已。这就是 type hints 的全部入门形式。修改后的完整代码见 docs_src/python_types/tutorial002_py310.py

def get_full_name(first_name: str, last_name: str):
    full_name = first_name.title() + " " + last_name.title()
    return full_name


print(get_full_name("john", "doe"))

需要注意,这与声明默认值完全是两回事。默认值写法是:

    first_name="john", last_name="doe"

那会用等号 =;而类型注解使用的是冒号 :。并且,添加 type hints 通常不会改变代码原本的运行行为——它们只是元信息,运行时不产生强制约束。

再次回到"正在编写这个函数"的场景:这一次你带着类型注解写代码,同一时刻按下 Ctrl+Space,编辑器就能给出基于 str 的方法补全:

添加 str 类型注解后,编辑器能够给出完整的字符串方法补全列表

滚动浏览这些选项,很快就能找到那个"看起来眼熟"的正确方法(例如 capitalize):

在补全列表中滚动查找首字母大写相关的方法

更进一步:来自类型检查的错误预警

再看一个已经带类型注解的函数,完整源码见 docs_src/python_types/tutorial003_py310.py

def get_name_with_age(name: str, age: int):
    name_with_age = name + " is this old: " + age
    return name_with_age

因为编辑器知道了每个变量的类型,它不仅能补全,还能做类型检查。此处 namestrageint,直接把两者用 + 拼接显然会出问题,编辑器会立刻标红提示:

编辑器检测到 str 与 int 直接拼接的类型错误并给出提示

现在你明白该修复它了——把 agestr(age) 转成字符串。修复后的版本见 docs_src/python_types/tutorial004_py310.py

def get_name_with_age(name: str, age: int):
    name_with_age = name + " is this old: " + str(age)
    return name_with_age

在哪里声明类型

你刚刚看到了 type hints 最主要的声明位置——函数参数。这也是之后在 FastAPI 中几乎唯一会用到的位置。除此之外,类型注解同样可以用于模块级变量、类属性等,语法完全一致。

简单类型

除了 str,所有标准 Python 类型都可以直接使用,例如:

  • int
  • float
  • bool
  • bytes

官方示例见 docs_src/python_types/tutorial005_py310.py,它在同一函数里混合使用了全部四种:

def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
    return item_a, item_b, item_c, item_d, item_e

这些简单类型恰好与 FastAPI 路由参数的基础校验一一对应:整数自动解析与校验、浮点、布尔、字节串,以及 OpenAPI 中对应的 schema 类型。

typing 模块

对于某些额外的使用场景,需要从标准库的 typing 模块导入一些辅助类型。比如想声明"参数可以是任意类型",就使用 typing.Any

from typing import Any


def some_function(data: Any):
    print(data)

注意 Any 会关闭该处的类型推断,编辑器不会再对该变量给出类型相关支持,属于必要时才使用的逃生通道。

泛型类型(Generic Types)

有些类型可以接收"写在方括号里的类型参数",用来定义其内部元素的类型。例如"由字符串组成的 list"应该声明为 list[str]

这类可以接收类型参数的类型被称为 Generic types(泛型)Generics。Python 内置类型可以原样当泛型使用(外面一层类型 + 方括号内元素类型):

  • list
  • tuple
  • set
  • dict

仓库中对应的系列示例源码集中在 docs_src/python_types/ 目录下(均为 Python 3.10+ 语法)。

List:字符串列表

先定义一个"strlist"类型的变量。声明变量用同样的冒号 : 语法;类型写成 list;由于 list 内部还有元素类型,把这些内部类型放进方括号:

def process_items(items: list[str]):
    for item in items:
        print(item)

(完整文件:docs_src/python_types/tutorial006_py310.py

说明:方括号里的这些内部类型被称为 type parameters(类型参数)。本例中 str 就是传给 list 的类型参数。

它的含义是:"变量 items 是一个 list,且其中每个元素都是 str"。这样声明之后,编辑器连遍历过程中的 item 都能给出正确支持:

在没有额外约束的情况下,编辑器通过 list[str] 推断出 item 也是 str 并给出方法补全

注意这里的变量 item 只是 items 里的一个元素,编辑器却已经知道它是 str 并针对它给出提示——在没有类型注解的情况下,这一点几乎不可能实现。同样的推断在 FastAPI 中意义重大:声明 items: list[str] 的请求体,框架会自动逐元素校验并转换。

Tuple 与 Set

tupleset 的声明方式类似,见 docs_src/python_types/tutorial007_py310.py

def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
    return items_t, items_s

这段代码的含义是:

  • 变量 items_t 是一个包含 3 个元素tuple,三个元素依次是 intintstr——元组的类型参数逐个对应每个位置的元素类型;
  • 变量 items_s 是一个 set,其中每个元素的类型是 bytes

Dict:键值对都要声明

声明 dict 需要传递 两个类型参数,用逗号分隔:第一个描述 dictkey 类型,第二个描述 value 类型。示例见 docs_src/python_types/tutorial008_py310.py

def process_items(prices: dict[str, float]):
    for item_name, item_price in prices.items():
        print(item_name)
        print(item_price)

含义是:

  • 变量 prices 是一个 dict
    • 它的 key 是 str 类型(例如每件商品的名称);
    • 它的 value 是 float 类型(例如每件商品的价格)。

FastAPI 正是靠这种容器注解来决定请求体 / 响应中嵌套结构的 schema,例如 dict[str, float] 会反映为 OpenAPI 中的 additionalProperties 约束。

Union:多类型联合

你可以声明一个变量可能是多种类型之一,例如既可能是 int 也可能是 str。定义方法是把两种类型用竖线 |(vertical bar,也叫 "bitwise or operator",但此处与位运算语义无关)分隔。因为变量可以属于这两个类型集合的并集,所以它叫 union。示例见 docs_src/python_types/tutorial008b_py310.py

def process_item(item: int | str):
    print(item)

即:item 可以是 int str

可能为 None 的可空类型

还可以声明某个值(比如 str可能为 None。Python 3.10+ 写法见 docs_src/python_types/tutorial009_py310.py

def say_hi(name: str | None = None):
    if name is not None:
        print(f"Hey {name}!")
    else:
        print("Hello World")

把单纯的 str 换成 str | None,编辑器就能帮你捕捉"想当然认为它永远是 str,实际却可能是 None"的那类错误。在 FastAPI 中,str | None = None 意味着该参数可选:传了按 str 校验,不传则为 None,而 str | None(无默认值)则一般表示"可传 null 但必须显式提供"。

把类当作类型使用

你同样可以把一个声明为变量的类型。假设有一个带 name 属性的类 Person,见 docs_src/python_types/tutorial010_py310.py

class Person:
    def __init__(self, name: str):
        self.name = name


def get_person_name(one_person: Person):
    return one_person.name

于是就可以把变量声明为 Person 类型,随后享受完整的编辑器支持——自动补全会列出 Person 的属性与方法(例如 name):

将变量声明为 Person 类类型后,编辑器自动补全其属性

要特别强调的是,one_person: Person 的意思是"one_person 是类 Person 的一个实例",而不是"one_person 是那个名为 Person 的类本身"。

Pydantic Models:用类描述数据形状

Pydantic 是一个用于数据校验的 Python 库:你先把数据的"形状"声明为带属性的类,每个属性都标有类型;然后用一组值创建该类的实例,Pydantic 就会校验这些值、必要时把它们转换成正确的类型,最终返回一个包含完整数据的对象——用这个对象时你同样拥有完整的编辑器支持。

Pydantic 是 FastAPI 的数据校验基础,二者深度绑定,仓库内 FastAPI 的请求体解析正是通过将 body 数据实例化为 Pydantic 模型对象来完成的。

取自 Pydantic 官方文档的典型示例(仓库版本见 docs_src/python_types/tutorial011_py310.py):

from datetime import datetime

from pydantic import BaseModel


class User(BaseModel):
    id: int
    name: str = "John Doe"
    signup_ts: datetime | None = None
    friends: list[int] = []


external_data = {
    "id": "123",
    "signup_ts": "2017-06-01 12:22",
    "friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123

注意观察这里发生的自动转换与校验:

  • id 声明为 int,传入字符串 "123" 被自动转成了整数 123
  • signup_ts 声明为 datetime | None,字符串 "2017-06-01 12:22" 被解析成了 datetime 对象;
  • friends 声明为 list[int],传入的 [1, "2", b"3"] 被逐元素校验并统一转换成 [1, 2, 3]

如果传入的数据与声明的类型完全不匹配(例如 id 传了 "abc"),Pydantic 会抛出校验错误。FastAPI 在此基础上更进一步:在 Web 请求/响应边界触发同样的校验流程,并把失败信息封装成带 422 状态码的标准错误响应返回给客户端。

带元数据注解的类型提示(Annotated)

Python 还有一个特性,允许通过 Annotated 在类型注解里附带额外的元数据(关于数据的数据,例如对类型的描述说明)。从 typing 中导入 Annotated 即可使用,示例见 docs_src/python_types/tutorial013_py310.py

from typing import Annotated


def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
    return f"Hello {name}"

Python 自身不会对 Annotated 做任何特殊处理;对编辑器和各类工具而言,这里的类型仍然是 str。但你可以把 Annotated 的位置利用起来,给 FastAPI 传递额外的元数据,从而定制应用的行为。

需要牢记的关键点是:传给 Annotated 的第一个类型参数才是真正的类型,其余部分对其他工具而言只是元数据。在源码层面,FastAPI 的 analyze_param 函数 正是用 typing.get_origin 判断注解是否为 Annotated,然后取出第一个类型参数作为实际类型,并从剩余参数中识别 FieldInfoDepends 等 FastAPI 专属注解。

之所以强调"这是标准 Python",意味着在你的编辑器、代码分析与重构工具里,你依然能得到尽可能好的开发体验,同时你的代码也能与大量其他 Python 工具与库保持高度兼容。

现在你只需要知道 Annotated 是标准 Python 语法、它存在即可;后续章节你会看到它到底有多强大。

FastAPI 如何利用 Type Hints

FastAPI 正是利用这些 type hints 完成了大量工作。当你用 type hints 声明参数后,你获得的是:

  • 编辑器支持(Editor support)
  • 类型检查(Type checks)

与此同时,FastAPI 复用同一份声明,自动替你完成:

  • 定义需求:从请求中提取 path 参数、query 参数、headers、body、dependencies 等的要求;
  • 转换数据:把请求数据转换成所需的目标类型;
  • 校验数据:校验每个请求带来的数据,并在数据不合法时生成自动错误返回给客户端;
  • 文档化 API:基于这些类型生成 OpenAPI schema,再被自动交互式文档 UI(Swagger UI / ReDoc)使用。

以上听起来可能有些抽象,但不用担心,在 Tutorial - User Guide 中你会看到这一切的实际运行。

源码视角:类型注解如何变成 API 能力

为了理解"复用同一份声明"究竟如何实现,可以看一下框架的依赖分析核心 fastapi/dependencies/utils.py

  • get_typed_signatureinspect.Signature 读取路由处理函数的参数签名。对于以字符串形式出现的注解(例如启用了 PEP 563 延迟求值的情形),先交由 ForwardRef 处理,再调用 evaluate_forwardref 求值成真实的类型对象——该能力在 fastapi/_compat/v2.py 中针对 Pydantic v2 实现;
  • analyze_param 对每个参数做类型分析:先剥离 Annotated(如上文所述取第一个类型参数),再识别参数来源于 Param/Body/Depends 等,并处理默认值语义。也就是说,一个 item_id: int 这样的普通注解,就能让 FastAPI 推断出它来自哪类请求位置、需要何种校验。

这正应了文档的结论:坚持使用标准 Python 类型、在同一个位置声明,而不需要额外的类或装饰器,FastAPI 就能替你完成大量工作。如果你在学完整个教程后想回顾更多类型知识,官方文档也推荐参考 mypy 提供的类型注解速查表(cheat sheet)作为补充资源。

小结

主题 语法要点 仓库示例
函数参数注解 first_name: str tutorial002
简单类型 int / float / bool / bytes tutorial005
任意类型 from typing import Any 正文代码
列表 list[str] tutorial006
元组与集合 tuple[int, int, str] / set[bytes] tutorial007
字典 dict[str, float] tutorial008
联合类型 int | str tutorial008b
可空类型 str | None tutorial009
类作为类型 one_person: Person tutorial010
Pydantic 模型 class User(BaseModel) tutorial011
带元数据注解 Annotated[str, ...] tutorial013

以上示例的源码均存放于 docs_src/python_types/ 目录(注意官方教程示例采用 Python 3.10+ 语法,这也对应 FastAPI 当前文档主线所要求的最低 Python 版本)。掌握这些基础后,下一步即可进入 Tutorial - User Guide,亲身实践"一份类型注解同时换来校验、转换、自动错误与文档"的 FastAPI 开发方式。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388