Python 类型提示简介¶
Python 支持可选的“类型提示”(也叫“类型注解”)。
这些“类型提示”或注解是一种特殊语法,用来声明变量的类型。
通过为变量声明类型,编辑器和工具可以为你提供更好的支持。
这只是一个关于 Python 类型提示的快速入门/复习。它只涵盖与 FastAPI 一起使用所需的最少部分...实际上非常少。
FastAPI 完全基于这些类型提示构建,它们带来了许多优势和好处。
但即使你从不使用 FastAPI,了解一些类型提示也会让你受益。
注意
如果你已经是 Python 专家,并且对类型提示了如指掌,可以跳到下一章。
动机¶
让我们从一个简单的例子开始:
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_name和last_name。 - 通过
title()将每个参数的第一个字母转换为大写。 - 用一个空格将它们拼接起来。
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"))
修改它¶
这是一个非常简单的程序。
但现在想象你要从零开始写它。
在某个时刻你开始定义函数,并且准备好了参数……
接下来你需要调用“那个把首字母变大写的方法”。
是 upper?是 uppercase?first_uppercase?还是 capitalize?
然后,你试试程序员的老朋友——编辑器的自动补全。
你输入函数的第一个参数 first_name,再输入一个点(.),然后按下 Ctrl+Space 触发补全。
但很遗憾,没有什么有用的提示:

添加类型¶
我们来改前一个版本的一行代码。
把函数参数从:
first_name, last_name
改成:
first_name: str, last_name: str
就是这样。
这些就是“类型提示”:
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"
这是两码事。
我们用的是冒号(:),不是等号(=)。
而且添加类型提示通常不会改变代码本来的行为。
现在,再想象你又在编写这个函数了,不过这次加上了类型提示。
在同样的位置,你用 Ctrl+Space 触发自动补全,就能看到:

这样,你可以滚动查看选项,直到找到那个“看着眼熟”的:

更多动机¶
看这个已经带有类型提示的函数:
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + age
return name_with_age
因为编辑器知道变量的类型,你不仅能得到补全,还能获得错误检查:

现在你知道需要修复它,用 str(age) 把 age 转成字符串:
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 类型。
例如:
intfloatboolbytes
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)
泛型类型¶
有些类型可以在方括号中接收“类型参数”(type parameters),用于声明其内部值的类型。比如“字符串列表”可以写为 list[str]。
这些能接收类型参数的类型称为“泛型类型”(Generic types)或“泛型”(Generics)。
你可以把相同的内建类型作为泛型使用(带方括号和内部类型):
listtuplesetdict
列表¶
例如,我们来定义一个由 str 组成的 list 变量。
用同样的冒号(:)语法声明变量。
类型写 list。
因为 list 是一种包含内部类型的类型,把内部类型写在方括号里:
def process_items(items: list[str]):
for item in items:
print(item)
信息
方括号中的这些内部类型称为“类型参数”(type parameters)。
在这个例子中,str 是传给 list 的类型参数。
这表示:“变量 items 是一个 list,并且列表中的每一个元素都是 str”。
这样,即使是在处理列表中的元素时,编辑器也能给你提供支持:

没有类型的话,这几乎是不可能做到的。
注意,变量 item 是列表 items 中的一个元素。
即便如此,编辑器仍然知道它是 str,并为此提供支持。
元组和集合¶
声明 tuple 和 set 的方式类似:
def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
return items_t, items_s
这表示:
- 变量
items_t是一个含有 3 个元素的tuple,分别是一个int、另一个int,以及一个str。 - 变量
items_s是一个set,其中每个元素的类型是bytes。
字典¶
定义 dict 时,需要传入 2 个类型参数,用逗号分隔。
第一个类型参数用于字典的键。
第二个类型参数用于字典的值:
def process_items(prices: dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
这表示:
- 变量
prices是一个dict:- 这个
dict的键是str类型(比如,每个条目的名称)。 - 这个
dict的值是float类型(比如,每个条目的价格)。
- 这个
Union¶
你可以声明一个变量可以是若干种类型中的任意一种,比如既可以是 int 也可以是 str。
定义时使用竖线(|)把两种类型分开。
这称为“联合类型”(union),因为变量可以是这两类类型集合的并集中的任意一个。
def process_item(item: int | str):
print(item)
这表示 item 可以是 int 或 str。
可能为 None¶
你可以声明一个值的类型是某种类型(比如 str),但它也可能是 None。
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)。
类作为类型¶
你也可以把类声明为变量的类型。
假设你有一个名为 Person 的类,带有 name:
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
然后你可以声明一个变量是 Person 类型:
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
接着,你会再次获得所有的编辑器支持:

注意,这表示“one_person 是类 Person 的一个实例(instance)”。
它并不表示“one_person 是名为 Person 的类本身(class)”。
Pydantic 模型¶
Pydantic 是一个用于执行数据校验的 Python 库。
你将数据的“结构”声明为带有属性的类。
每个属性都有一个类型。
然后你用一些值创建这个类的实例,它会校验这些值,并在需要时把它们转换为合适的类型,返回一个包含所有数据的对象。
你还能对这个结果对象获得完整的编辑器支持。
下面是来自 Pydantic 官方文档的一个示例:
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 完全建立在 Pydantic 之上。
你会在教程 - 用户指南中看到更多的实战示例。
带元数据注解的类型提示¶
Python 还提供了一个特性,可以使用 Annotated 在这些类型提示中放入额外的元数据。
你可以从 typing 导入 Annotated。
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 中,用类型提示来声明参数,你将获得:
- 编辑器支持。
- 类型检查。
……并且 FastAPI 会使用相同的声明来:
- 定义要求:从请求路径参数、查询参数、请求头、请求体、依赖等。
- 转换数据:把请求中的数据转换为所需类型。
- 校验数据:对于每个请求:
- 当数据无效时,自动生成错误信息返回给客户端。
- 使用 OpenAPI 记录 API:
- 然后用于自动生成交互式文档界面。
这些听起来可能有点抽象。别担心。你会在教程 - 用户指南中看到所有这些的实际效果。
重要的是,通过使用标准的 Python 类型,而且只在一个地方声明(而不是添加更多类、装饰器等),FastAPI 会为你完成大量工作。
信息
如果你已经读完所有教程,又回来想进一步了解类型,一个不错的资源是 mypy 的“速查表”。