Python 类型简介¶
Python 支持可选的“类型提示”(也称为“类型注解”)。
这些“类型提示”或注解是一种特殊的语法,允许声明变量的类型。
通过为变量声明类型,编辑器和工具可以为您提供更好的支持。
这只是一个关于 Python 类型提示的快速教程/复习。它仅涵盖了与 FastAPI 一起使用所需的最低要求……实际上非常少。
FastAPI 完全基于这些类型提示,它们赋予它许多优势和好处。
但即使您从不使用 FastAPI,学习一些关于它们的知识也会让您受益。
Note
如果您是 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 一起使用它们的主要位置。
简单类型¶
您可以声明所有标准的 Python 类型,不仅仅是 str。
例如,您可以使用:
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_d, item_e
带有类型参数的泛型类型¶
有一些数据结构可以包含其他值,比如 dict、list、set 和 tuple。并且内部值也可以有自己的类型。
这些具有内部类型的类型称为“泛型”类型。并且可以声明它们,甚至包括它们的内部类型。
要声明这些类型及其内部类型,您可以使用标准的 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。
由于列表是一种包含某些内部类型的类型,您将它们放在方括号中:
def process_items(items: list[str]):
for item in items:
print(item)
从 typing 导入 List(大写 L):
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
使用相同的冒号(:)语法声明变量。
作为类型,放入从 typing 导入的 List。
由于列表是一种包含某些内部类型的类型,您将它们放在方括号中:
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
Info
方括号中的那些内部类型称为“类型参数”。
在这种情况下,str 是传递给 List(或 Python 3.9 及以上的 list)的类型参数。
这意味着:“变量 items 是一个 list,并且此列表中的每个项都是 str”。
Tip
如果您使用 Python 3.9 或更高版本,您不必从 typing 导入 List,您可以使用相同的常规 list 类型代替。
通过这样做,您的编辑器甚至可以在处理列表中的项时提供支持:

没有类型,这几乎是不可能实现的。
请注意,变量 item 是列表 items 中的一个元素。
并且,编辑器仍然知道它是 str,并为此提供支持。
元组和集合¶
您会做同样的事情来声明 tuple 和 set:
def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
return items_t, items_s
from typing import Set, Tuple
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 个类型参数,用逗号分隔。
第一个类型参数用于 dict 的键。
第二个类型参数用于 dict 的值:
def process_items(prices: dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
from typing import Dict
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类型(比如说,每个项的价格)。
- 这个
联合¶
您可以声明一个变量可以是几种类型中的任何一种,例如,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¶
如果您使用的 Python 版本低于 3.10,这里有一个来自我非常主观的观点的提示:
- 🚨 避免使用
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 这样的名称了。😎
泛型类型¶
这些在方括号中接受类型参数的类型称为泛型类型或泛型,例如:
您可以使用相同的内置类型作为泛型(带有方括号和内部类型):
listtuplesetdict
并且与 Python 3.8 相同,来自 typing 模块:
UnionOptional(与 Python 3.8 相同)- ……以及其他。
在 Python 3.10 中,作为使用泛型 Union 和 Optional 的替代方案,您可以使用竖线(|)来声明类型的联合,这更好且更简单。
您可以使用相同的内置类型作为泛型(带有方括号和内部类型):
listtuplesetdict
并且与 Python 3.8 相同,来自 typing 模块:
UnionOptional- ……以及其他。
ListTupleSetDictUnionOptional- ……以及其他。
类作为类型¶
您也可以声明一个类作为变量的类型。
假设您有一个类 Person,带有一个名称:
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 的一个实例”。
这并不意味着“one_person 是名为 Person 的类”。
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
from datetime import datetime
from typing import Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[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
from datetime import datetime
from typing import List, Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[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
Info
要了解更多关于 Pydantic 的信息,请查看其文档。
FastAPI 完全基于 Pydantic。
您将在 教程 - 用户指南 中看到更多所有这些实践。
Tip
当您使用 Optional 或 Union[Something, None] 而没有默认值时,Pydantic 有一种特殊行为,您可以在 Pydantic 文档中阅读更多关于 必需的可选字段 的信息。
带有元数据注解的类型提示¶
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}"
在低于 Python 3.9 的版本中,您从 typing_extensions 导入 Annotated。
它已经随 FastAPI 安装。
from typing_extensions 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。😎
稍后您将看到它有多么强大。
Tip
这是标准 Python 的事实意味着您仍然将在编辑器中获得最佳的开发人员体验,使用您用于分析和重构代码的工具等。✨
并且您的代码将与许多其他 Python 工具和库非常兼容。🚀
FastAPI 中的类型提示¶
FastAPI 利用这些类型提示来做几件事。
使用 FastAPI,您使用类型提示声明参数,然后您获得:
- 编辑器支持。
- 类型检查。
……并且 FastAPI 使用相同的声明来:
- 定义要求:来自请求路径参数、查询参数、头、体、依赖项等。
- 转换数据:从请求到所需类型。
- 验证数据:来自每个请求:
- 当数据无效时生成自动错误返回给客户端。
- 使用 OpenAPI 记录 API:
- 然后被自动交互式文档用户界面使用。
这一切可能听起来很抽象。别担心。您将在 教程 - 用户指南 中看到所有这些的实际操作。
重要的是,通过使用标准 Python 类型,在一个地方(而不是添加更多类、装饰器等),FastAPI 将为您完成大量工作。
Info
如果您已经完成了所有教程,并回来查看有关类型的更多信息,一个好的资源是 mypy 的“备忘单”。