Python 类型提示简介¶
🌐 Translation by AI and humans
This translation was made by AI guided by humans. 🤝
It could have mistakes of misunderstanding the original meaning, or looking unnatural, etc. 🤖
You can improve this translation by helping us guide the AI LLM better.
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"))
🤓 Other versions and variants
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"))
🤓 Other versions and variants
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"))
🤓 Other versions and variants
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
🤓 Other versions and variants
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
🤓 Other versions and variants
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
🤓 Other versions and variants
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
带类型参数的泛型类型¶
有些数据结构可以包含其他值,比如 dict、list、set 和 tuple。而内部的值也会有自己的类型。
这些带有内部类型的类型称为“泛型”(generic)类型。可以把它们连同内部类型一起声明出来。
要声明这些类型以及内部类型,你可以使用 Python 标准库模块 typing。它就是为支持这些类型提示而存在的。
更新的 Python 版本¶
使用 typing 的语法与所有版本兼容,从 Python 3.6 到最新版本(包括 Python 3.9、Python 3.10 等)。
随着 Python 的发展,更新的版本对这些类型注解的支持更好,在很多情况下你甚至不需要导入和使用 typing 模块来声明类型注解。
如果你可以为项目选择更高版本的 Python,你将能享受到这种额外的简化。
在整个文档中,会根据不同 Python 版本提供相应的示例(当存在差异时)。
比如“Python 3.6+”表示兼容 Python 3.6 及以上(包括 3.7、3.8、3.9、3.10 等)。而“Python 3.9+”表示兼容 Python 3.9 及以上(包括 3.10 等)。
如果你可以使用最新的 Python 版本,请使用最新版本的示例,它们将拥有最简洁的语法,例如“Python 3.10+”。
列表¶
例如,我们来定义一个由 str 组成的 list 变量。
用同样的冒号(:)语法声明变量。
类型写 list。
因为 list 是一种包含内部类型的类型,把内部类型写在方括号里:
def process_items(items: list[str]):
for item in items:
print(item)
🤓 Other versions and variants
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
🤓 Other versions and variants
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)
🤓 Other versions and variants
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。
在 Python 3.6 及以上(包括 Python 3.10),你可以使用 typing 中的 Union,把可能的类型放到方括号里。
在 Python 3.10 中还有一种新的语法,可以用竖线(|)把可能的类型分隔开。
def process_item(item: int | str):
print(item)
from typing import Union
def process_item(item: Union[int, str]):
print(item)
两种方式的含义一致:item 可以是 int 或 str。
可能为 None¶
你可以声明一个值的类型是某种类型(比如 str),但它也可能是 None。
在 Python 3.6 及以上(包括 Python 3.10),你可以通过从 typing 模块导入并使用 Optional 来声明:
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
使用 Optional[str] 而不是仅仅 str,可以让编辑器帮助你发现把值当成总是 str 的错误(实际上它也可能是 None)。
Optional[Something] 实际上是 Union[Something, None] 的简写,它们等价。
这也意味着在 Python 3.10 中,你可以使用 Something | None:
def say_hi(name: str | None = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Union
def say_hi(name: Union[str, None] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
使用 Union 或 Optional¶
如果你使用的是 3.10 以下的 Python 版本,这里有个来自我非常主观的建议:
- 🚨 避免使用
Optional[SomeType] - 改用 ✨
Union[SomeType, None]✨
两者等价,底层相同,但我更推荐 Union 而不是 Optional,因为“optional(可选)”这个词看起来像是在说“参数可选”,而它实际上表示“它可以是 None”,即使它并不是可选的,仍然是必填的。
我认为 Union[SomeType, None] 更明确地表达了它的意思。
这只是关于词语和名字。但这些词会影响你和你的队友如何理解代码。
例如,看下面这个函数:
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
🤓 Other versions and variants
def say_hi(name: str | None):
print(f"Hey {name}!")
参数 name 被定义为 Optional[str],但它并不是“可选”的,你不能不传这个参数就调用函数:
say_hi() # 哦不,这会抛错!😱
参数 name 仍然是必填的(不是“可选”),因为它没有默认值。不过,name 接受 None 作为值:
say_hi(name=None) # 这样可以,None 是有效值 🎉
好消息是,一旦你使用 Python 3.10,就无需再为此操心,因为你可以直接用 | 来定义类型联合:
def say_hi(name: str | None):
print(f"Hey {name}!")
🤓 Other versions and variants
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
这样你就不必再考虑 Optional 和 Union 这些名字了。😎
泛型类型¶
这些在方括号中接收类型参数的类型称为“泛型类型”(Generic types)或“泛型”(Generics),例如:
你可以把同样的内建类型作为泛型使用(带方括号和内部类型):
listtuplesetdict
以及与之前的 Python 版本一样,来自 typing 模块的:
UnionOptional- ……以及其他。
在 Python 3.10 中,作为使用泛型 Union 和 Optional 的替代,你可以使用竖线(|)来声明类型联合,这更好也更简单。
你可以把同样的内建类型作为泛型使用(带方括号和内部类型):
listtuplesetdict
以及来自 typing 模块的泛型:
UnionOptional- ……以及其他。
类作为类型¶
你也可以把类声明为变量的类型。
假设你有一个名为 Person 的类,带有 name:
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
🤓 Other versions and variants
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
🤓 Other versions and variants
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 之上。
你会在教程 - 用户指南中看到更多的实战示例。
提示
当你在没有默认值的情况下使用 Optional 或 Union[Something, None] 时,Pydantic 有一个特殊行为,你可以在 Pydantic 文档的 必填的 Optional 字段 中了解更多。
带元数据注解的类型提示¶
Python 还提供了一个特性,可以使用 Annotated 在这些类型提示中放入额外的元数据。
从 Python 3.9 起,Annotated 是标准库的一部分,因此可以从 typing 导入。
from typing import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
🤓 Other versions and variants
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 的“速查表”。