首页
/ FastAPI 类型提示实战指南:从 Python 类型声明、泛型到 Annotated 的完整解析

FastAPI 类型提示实战指南:从 Python 类型声明、泛型到 Annotated 的完整解析

2026-09-06 17:01:19作者:瞿蔚英Wynne

本文基于 FastAPI 官方文档 python-types.md 展开,系统讲解 Python 类型提示(Type Hints)的动机、声明方式、泛型类型(list/tuple/set/dict)、联合类型(Union)、类作为类型、Pydantic 模型与 Annotated 元数据标注,并结合当前仓库源码说明 FastAPI 如何将这些标准类型声明转化为参数校验、数据转换与 OpenAPI 自动文档的底层机制。读完后,你将掌握用最少语法获得编辑器智能提示、静态类型检查与 API 自动校验能力的完整方法。

编辑器在类型声明后给出的自动补全效果

动机:类型提示能解决什么问题

Python 支持可选的"类型提示"(type hints,也叫 type annotations)。这是一种特殊的语法,允许你声明一个变量的类型,例如 strintfloatbool

为变量声明类型后,编辑器和其他工具就能提供更好的支持。而 FastAPI 正是完全建立在这些类型提示之上的——这是它诸多优势的根基。即使你从不用 FastAPI,了解类型提示也大有裨益。

先看一个简单例子(对应源码 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, last_name

改为:

    first_name: str, last_name: str

仅此而已,这就是"类型提示"(对应源码 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

注意,这与声明默认值是完全不同的事,比如这样写:

    first_name="john", last_name="doe"

两者是两码事:类型提示用的是冒号(:,而不是等号(=)。并且加上类型提示通常不会改变程序的实际运行行为——它不影响运行时逻辑,而是服务于编辑器和静态检查工具。

再想象你在写同一个函数的过程中,这次已经加上了类型提示。在同一位置用 Ctrl+Space 触发自动补全,你会看到完整的字符串方法列表。

加上类型后,编辑器列出 str 的完整方法

你可以滚动选项,直到找到那个"似曾相识"的 title()

更多动机:类型检查帮你发现 Bug

再看一个已经带有类型提示的函数(对应源码 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

由于编辑器知道每个变量的类型,你不仅获得补全,还能获得错误检查

编辑器检测到 int 不能直接与 str 拼接,标红报错

编辑器提示你:ageint,不能直接用 + 与字符串拼接。此时你知道必须修复它——用 str(age)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

声明类型:函数参数是最主要的场景

前面看到的函数参数,就是声明类型提示的主要位置,也是你在 FastAPI 中最常用的位置

简单类型

你可以声明所有标准的 Python 内置类型,不只是 str。例如可以使用(对应源码 docs_src/python_types/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 模块

对于一些额外用途,你可能需要从标准库 typing 模块导入一些内容。例如,当你想声明"某变量可以是任意类型"时,可以使用 typing 中的 Any

from typing import Any


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

泛型类型(Generic types)

有些类型可以用方括号携带"类型参数",用于定义其内部元素的类型,例如"字符串列表"声明为 list[str]。能够接收类型参数的类型被称为泛型类型(Generic types / Generics)

内置的泛型类型同样用方括号加类型的方式使用:

  • list
  • tuple
  • set
  • dict

List

例如定义一个变量为 str 类型的 list。用同样的冒号(:)语法声明变量,类型写 list;由于 list 是包含内部类型的类型,把内部类型放在方括号里(对应源码 docs_src/python_types/tutorial006_py310.py):

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

说明:方括号里的这些内部类型被称为"类型参数"。这里 str 就是传给 list 的类型参数。

这表示:"变量 items 是一个 list,且这个列表中的每个元素都是 str"。这样做到位的后果是:即使你在遍历列表处理元素时,编辑器依然能提供完整的智能支持——变量 item 是列表 items 中的元素之一,但编辑器知道它是 str,并据此提供方法补全。没有类型提示时,这种支持几乎不可能实现。

Tuple and 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 时传入两个类型参数,用逗号分隔:第一个参数描述键(keys)的类型,第二个参数描述值(values)的类型(对应源码 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,其中键是 str 类型(例如每个商品的名称),值是 float 类型(例如每个商品的价格)。

Union(联合类型)

你可以声明一个变量可以是多种类型之一,例如 intstr。使用**竖线(|,即 union 运算符,也即按位或运算符,但这里取其"并集"含义)**分隔两个类型(对应源码 docs_src/python_types/tutorial008b_py310.py):

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

这称为"union(联合)",因为变量可以是这两个类型集合的并集中的任何值——即 item 可以是 int,也可以是 str

可能为 None(Possibly None)

你可以声明一个值可以是某种类型(比如 str),但也可能是 None(对应源码 docs_src/python_types/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 | None 而不是只用 str,可以让编辑器帮你发现那些"默认认为值永远是 str、但实际可能为 None"的潜在错误。

类作为类型(Classes as types)

你也可以把声明为变量的类型。假设有类 Person,带有一个 name 属性(对应源码 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

注意:one_person: Person 表示 "one_person 是类 Person 的一个实例(instance)",而不是"one_personPerson 这个本身"。再次强调,这样写之后,访问 one_person.name 等属性时你能获得完整的编辑器支持。

Pydantic 模型:类型提示 + 数据校验

Pydantic 是一个用于数据校验(data validation)的 Python 库。你通过"带属性的类"声明数据的"形状(shape)",而每个属性都有类型。然后用一些值创建该类的实例,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",Pydantic 自动转换为 int
  • signup_ts 被声明为 datetime | None,传入的字符串 "2017-06-01 12:22" 被解析为 datetime 对象;
  • friends: list[int] 中传入的 "2"b"3" 都被转换为 int

FastAPI 完全基于 Pydantic 构建,你可以在 教程 - 用户指南 中看到更多实践内容。

Annotated:给类型提示附加元数据

Python 还提供了 Annotated 机制,允许你在类型提示中放入额外的元数据(metadata,即"关于数据的数据",此处指关于类型的信息,如描述文字)Annotated 可以从 typing 导入(对应源码 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 的第一个类型参数才是真正的类型,其余的全部是给其他工具用的元数据。现阶段你只需知道 Annotated 的存在、并且它是标准 Python 语法即可——后面你会看到它多么强大。

提示:正因为它是标准 Python,你在编辑器中依然能获得最好的开发体验,代码分析、重构工具照常工作,并且你的代码与大量其他 Python 工具、库保持高兼容性。

类型提示在 FastAPI 中的角色

FastAPI 利用这些类型提示来完成多件事情。在 FastAPI 中,你用类型提示声明参数,由此获得:

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

……而 FastAPI同一份声明来完成:

  • 定义需求(requirements):从请求的路径参数(path parameters)、查询参数(query parameters)、请求头(headers)、请求体(body)、依赖(dependencies)等处获取什么数据;
  • 转换数据(data conversion):把请求中的原始数据转换为所要求的类型;
  • 校验数据(data validation):对每个请求的数据进行校验,数据无效时自动生成错误并返回给客户端
  • 文档化(documentation):用 OpenAPI 为 API 生成文档,进而驱动自动交互文档用户界面(Swagger UI / ReDoc)。

这些听起来可能很抽象,不必担心,教程 - 用户指南 中你会看到它们全部付诸实践。重要的是:通过在同一个地方使用标准 Python 类型(而不是再额外添加类、装饰器等),FastAPI 会替你完成大量工作

源码层面的印证:类型提示如何驱动 FastAPI 的核心链路

结合当前仓库的源码结构可以印证上述机制。FastAPI 的类型提示处理分布在几个关键模块中:

  • 参数定义层fastapi/param_functions.py 中的 Path()Query()Header()Cookie()Form() 等函数,以及 fastapi/params.py 中对应的参数类,正是你在路由函数里与类型提示配合使用的声明方式——例如 name: str = Query(..., min_length=3)Query 返回的对象会被放进 Annotated[str, Query(...)] 的元数据位置;
  • 依赖解析与数据校验层fastapi/dependencies/ 目录下的工具模块负责解析每个路由函数的类型签名,把请求数据按类型提示进行提取、转换和校验,校验失败时抛出 RequestValidationError,由默认的异常处理器统一转换为返回给客户端的 JSON 错误;
  • OpenAPI 文档层fastapi/openapi/ 目录下的工具模块根据同样的类型声明生成 OpenAPI 3.1 schema,这些 schema 最终被 Swagger UI 等交互式文档界面消费;
  • 安全方案fastapi/security/ 目录提供 API Key、HTTP Basic/Bearer、OAuth2 等方案,它们同样以类型提示 + Annotated 元数据的形式挂载到路由参数上。

也就是说,从"声明参数类型"到"自动校验、自动文档"的整条链路,起点都是你在路由函数签名里写下的那一个 : strAnnotated[str, Query(...)]。这也正是 FastAPI 文档反复强调"标准 Python 类型是唯一需要写的声明"的底层原因。

从源码结构看,类型信息的传递路径是:路由函数签名(Annotated/普通类型提示)→ 依赖解析(fastapi/dependencies/)→ 参数提取与 Pydantic 校验 → 响应序列化;同时同一份类型信息被 fastapi/openapi/ 独立消费为 OpenAPI schema,两条链路共享同一声明来源。

小结

  • 类型提示使用冒号(:)而非等号(=,不影响运行时行为,却能让编辑器和静态工具提供补全与错误检查;
  • 内置的 listtuplesetdict 都是泛型,可携带类型参数;dict 需要键、值两个参数;| 表示联合类型,str | None 表示可能为空值;
  • 类名本身也是类型,声明的是"类的实例"而非"类";
  • Pydantic 模型把类型提示升级为"数据校验 + 自动类型转换",FastAPI 完全构建于其上;
  • Annotated 是标准 Python 语法,第一个参数是真实类型,其余是元数据——FastAPI 正是通过它接收 QueryPath、安全方案等附加配置;
  • 在 FastAPI 中,一份标准类型声明同时服务于编辑器支持、数据转换、数据校验与 OpenAPI 自动文档,无需额外的装饰器或配置类。

如果你想系统回顾类型提示语法,官方文档提到 mypy 的 cheat sheet 是不错的参考资料;更多实践内容请继续阅读 教程 - 用户指南

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

项目优选

收起
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++
915
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