Python 类型注解(Type Hints)入门:驱动 FastAPI 声明式 API 的基石
2026/9/8 18:40:45 网站建设 项目流程

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

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

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触发补全。可惜的是,编辑器一无所知,弹出的列表里什么有用的东西都没有:

问题的根源在于: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的方法补全:

滚动浏览这些选项,很快就能找到那个"看起来眼熟"的正确方法(例如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,直接把两者用+拼接显然会出问题,编辑器会立刻标红提示:

现在你明白该修复它了——把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都能给出正确支持:

注意这里的变量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可以是intstr

可能为 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):

要特别强调的是,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_signature 用inspect.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: strtutorial002
简单类型int/float/bool/bytestutorial005
任意类型from typing import Any正文代码
列表list[str]tutorial006
元组与集合tuple[int, int, str]/set[bytes]tutorial007
字典dict[str, float]tutorial008
联合类型int \| strtutorial008b
可空类型str \| Nonetutorial009
类作为类型one_person: Persontutorial010
Pydantic 模型class User(BaseModel)tutorial011
带元数据注解Annotated[str, ...]tutorial013

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

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询