首页
/ FastAPI 入门基石:一文吃透 Python 类型注解(Type Hints)及其驱动机制

FastAPI 入门基石:一文吃透 Python 类型注解(Type Hints)及其驱动机制

2026-09-07 16:23:23作者:伍霜盼Ellen

类型注解(Type Annotations / Type Hints)是 Python 中一套可选的声明式语法,用于标注变量的数据类型;而 FastAPI 恰好是建立在这些类型注解之上运行的高性能 Web 框架。本文基于当前仓库的官方入门文档 docs/fr/docs/python-types.md(配套代码位于 docs_src/python_types/)整理成篇,从最朴素的"为什么要写类型"出发,逐步讲清简单类型、泛型容器、UnionNone 可选值、自定义类、Pydantic 模型以及 Annotated 元数据注解,最后结合 fastapi/dependencies/utils.py 源码说明 FastAPI 究竟如何把一行类型标注转化为参数定义、数据校验、类型转换与自动 API 文档。读完本文,你将彻底看懂 FastAPI 代码里那些"参数名后面跟冒号和类型"的写法,并具备阅读 FastAPI 完整教程的基础。

下面两张截图取自仓库英文文档图库 docs/en/docs/img/python-types/,直观展示了"有无类型注解"对编辑器体验的差异,后文会逐一拆解其原因:

编辑器在无类型注解的函数参数上触发自动补全,几乎得不到任何有用的候选

当参数带有类型注解后,编辑器会给出类型错误提示,例如把 int 当字符串拼接

动机:类型注解究竟解决了什么

先看一个最简单的函数(对应示例文件 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() 把各自首字母转成大写,再把两者用空格拼接。它是能正确运行的,问题出在"写它的过程"里。

编辑器帮不上忙的困境

设想你要从零编写这个函数:参数已经准备好,但你在某个瞬间忘了"把首字母大写"的方法到底叫什么。是 upper?还是 uppercasefirst_uppercase?或者 capitalize

于是你求助程序员的忠实伙伴——编辑器的自动补全。你敲下第一个参数 first_name,输入点号 . 并按 Ctrl+Space 触发补全。可是,由于编辑器根本不知道 first_name 是什么类型,它无从得知 str 上有哪些可用的方法,结果你不会得到任何有用的候选项。

加一行类型就完全不同

现在只修改一处:给函数参数补上类型注解(对应示例文件 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, last_name

改成

    first_name: str, last_name: str

仅此而已。冒号(:)后面的 str 就是类型注解。注意两点区分:

  • 不是默认值。默认值用等号(=)表达,例如 first_name="john", last_name="doe"
  • 加不加类型注解,程序的运行行为一般不会改变,它更像是写给编辑器、静态检查工具和其他开发者看的"契约"。

但这一个改动足以让编辑器在再次触发自动补全时,基于"first_namestr"这一信息,过滤出 str 真正拥有的方法列表,供你滚动查找、直至选中那个"看着眼熟"的正确方法。

类型注解还能发现运行期才会炸的错误

再看一个已经带注解的函数(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,它会在你把 int 直接拼到字符串上时立刻亮出类型错误提示(上方的 image04 截图即为此例)。而 Python 解释器要等真正执行到这一行才会抛出 TypeError。修复方式也很简单:用 str(age) 做显式转换(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

这就是类型注解的第一大价值:把"运行时才暴露"的错误提前到"敲代码时"发现

声明类型:五种基础写法

函数参数是声明类型注解最主要的场所,而它也正是你在 FastAPI 中使用类型注解的最主要场所。

简单类型

除了 str,Python 的所有内置简单类型都可以直接作为注解,例如 tutorial005_py310.py 所示:

  • int
  • float
  • bool
  • bytes
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

需要兜底类型时:typing.Any

标准库 typing 模块覆盖了一些补充场景。例如希望表达"这个值可以是任意类型"时,可以用 Any

from typing import Any


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

泛型类型(Generics):给容器标注内部元素类型

某些类型可以接收"类型参数"(方括号内的内部类型),例如"字符串列表"可写为 list[str]。这类可以带类型参数的类型称为泛型类型(Generics)。Python 内置容器都能当泛型用:

  • list
  • tuple
  • set
  • dict

list:列表

声明一个"元素全是 str 的列表"(tutorial006_py310.py):

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

语法上仍是冒号 : 加类型;因为 list 内部还要装具体类型,所以把内部类型放进方括号 [...]。这里的 str 就是传给 list类型参数。整句含义为:items 是一个 list,其中每个元素都是 str

它的收益在遍历元素时尤其明显:循环变量 item 只是列表里的一个元素,你并没有单独给它写注解,但编辑器基于 list[str] 已经能推断出 itemstr,从而在 item. 后面照常给出字符串方法的补全。没有类型信息时,这种体验几乎不可能实现。

tupleset:元组与集合

同样的思路可以声明元组与集合(tutorial007_py310.py):

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

含义分别是:

  • items_t 是一个 tuple,它有 3 个元素,依次是 intintstr
  • items_s 是一个 set,其中每个元素都是 bytes

注意 tuple 的类型参数要按元素逐一列出(数量和位置都对应元素顺序),而 set 只需写一个元素类型。

dict:字典要传两个类型参数

dict 需要两个类型参数、用逗号分隔:第一个是键(key)的类型,第二个是值(value)的类型。例如 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,其键为 str(比如每个商品的名字),值为 float(比如每个商品的价格)。

Union:一个变量可以是多个类型

当变量既可能是 int 又可能是 str 时,可以用竖线 | 分隔多个类型(tutorial008b_py310.py):

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

这就是所谓的"联合类型(Union)"——变量可以取这些类型集合的并集中的任意一个。| 在 Python 中本是"按位或"运算符,但在类型注解语境下它表示"或"这一类型关系,请勿混淆其原始语义。

可空类型:值还可能为 None

进一步地,你可以声明某个值"通常是 str,但也可能None"(tutorial009_py310.py,Python 3.10+ 语法):

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"的分支——这正是 Python 开发中最常见的 AttributeError: 'NoneType' object has no attribute ... 的来源。

类也可以当类型

类型注解不仅限于内置类型,任何都能充当注解。比如定义一个有 namePerson 类(tutorial010_py310.py):

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


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

声明 one_person: Person 之后,编辑器就能在 one_person. 后面给出 Person 的属性与方法补全。需要精确理解这行注解的语义:它表示 one_personPerson 类的一个实例,而不是"one_person 就是名为 Person 的那个类对象本身"。

Pydantic 模型:把类型注解升级为数据校验

Pydantic 是 Python 生态中做数据校验的库,也是 FastAPI 的底层依赖。用它的方式,是你以"带类型属性的类"来声明数据的"形状",每个属性都有一个类型;随后用实际值创建该类实例时,Pydantic 会校验这些值、必要时把它们转换为正确类型,并最终给你一个携带全部数据的对象。

官方文档中给出的经典示例即为仓库内的 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

这段代码直观体现了 Pydantic 的三层能力,也预告了 FastAPI 的行为模型:

  • 类型转换:入参里的 "id": "123"(字符串)被转成 int 123;friends 里的 "2"b"3" 被统一转成整数 23
  • 默认值namesignup_ts 未显式传入时,分别落到默认值 "John Doe"None
  • 类型化对象:最终得到的 user 是类型安全的 User 实例,编辑器能对 user.iduser.name 提供完整的补全与类型检查。

FastAPI 完全构建在 Pydantic 之上:你在 FastAPI 里定义请求体模型的方式,与上面这段代码几乎如出一辙。

Annotated:给类型注解附加元数据

Python 还提供了一种把额外元数据写进类型注解的能力——Annotated(从 typing 导入),例如 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 的第一个类型参数才是真正的类型,其余内容都只是留给其他工具的元数据。本文只需知道 Annotated 存在、且它属于纯标准 Python 语法即可——这一点意味着:使用它时你依然能获得编辑器、重构与静态分析工具的全部支持,代码也能与大量其他 Python 工具和库良好兼容。

FastAPI 如何"消费"类型注解:从编辑器提示到自动文档

回到本文的核心问题:为什么 FastAPI 如此依赖类型注解?因为同样的一个 name: str 标注,对 FastAPI 而言是多用途的:

  • 编辑器支持:你的 IDE 能即时给出类型提示与补全;
  • 类型检查:静态分析工具能发现类型误用;
  • 定义请求前提:FastAPI 从类型注解出发,识别路径参数、查询参数、请求头、请求体、依赖项等——参数标注决定它归属哪一类请求来源;
  • 数据转换:把来自 HTTP 请求的字符串数据按注解转换为目标类型(如把 "123" 解析成 int);
  • 数据校验:对每个请求的数据做校验,数据非法时自动生成返回给客户端的错误信息;
  • API 文档化:类型与模型被用于生成 OpenAPI 规范,进而驱动 Swagger UI 与 ReDoc 等交互式文档界面。

这一设计可以在源码层面得到印证:FastAPI 在解析端点函数时,会借助 inspect.signature 提取经过求值(eval_str=True,即支持字符串形式前向引用)的函数签名,从而拿到每个参数的类型注解。相关实现位于 fastapi/dependencies/utils.py

  • 第 200–213 行:get_typed_signature 等函数基于 inspect.signature(call, eval_str=True) 统一获取函数签名,作为后续参数解析的输入;
  • 第 292 行:endpoint_signature = get_typed_signature(call),即端点函数一被注册,其签名立刻被解析。

配合 Pydantic 模型做请求体验证与响应序列化,FastAPI 最终做到:你只需在一个地方(类型注解)声明意图,而不必像传统框架那样额外编写参数解析类、装饰器或重复的校验逻辑,FastAPI 就会替你做掉大部分脏活。

如果此前已通读全部教程、只想针对"类型系统"再看一遍,官方文档还推荐了 mypy 的类型速查表作为延伸资料;而对刚起步的读者来说,更自然的下一步是直接进入 Tutoriel - Guide utilisateur,在真实的路径参数、查询参数与请求体示例中观察类型注解被 FastAPI 逐项"变现"的全过程。

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

项目优选

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