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中使用它们的主要位置。
简单类型¶
您可以声明所有标准的 Python 类型,不仅仅是 str。
您可以例如使用
整数 (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
带有类型参数的泛型类型¶
有些数据结构可以包含其他值,例如 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+”。
列表 (List)¶
例如,让我们定义一个变量为 str 的 list。
声明变量,使用相同的冒号 (:) 语法。
作为类型,放入 list。
由于列表是一种包含某些内部类型的类型,因此您将它们放在方括号内
def process_items(items: list[str]):
for item in items:
print(item)
信息
方括号内的这些内部类型称为“类型参数”。
在这种情况下,str 是传递给 list 的类型参数。
这意味着:“变量 items 是一个 list,并且此列表中的每个项目都是一个 str”。
通过这样做,您的编辑器甚至可以在处理列表中的项目时提供支持

没有类型,这几乎是不可能实现的。
请注意,变量 item 是列表 items 中的一个元素。
尽管如此,编辑器知道它是一个 str,并为此提供支持。
元组 (Tuple) 和集合 (Set)¶
您将以相同的方式声明 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)¶
要定义一个 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)
这意味着
- 变量
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¶
如果您使用的 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}!")
🤓 其他版本和变体
def say_hi(name: str | None):
print(f"Hey {name}!")
参数 name 被定义为 Optional[str],但它不是可选的,您不能在没有该参数的情况下调用函数
say_hi() # Oh, no, this throws an error! 😱
name 参数仍然是必需的(不是可选的),因为它没有默认值。尽管如此,name 接受 None 作为值
say_hi(name=None) # This works, None is valid 🎉
好消息是,一旦您使用了 Python 3.10,您就不必担心这个问题了,因为您将能够简单地使用 | 来定义类型的联合
def say_hi(name: str | None):
print(f"Hey {name}!")
🤓 其他版本和变体
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
然后您就不必担心像 Optional 和 Union 这样的名称了。 😎
泛型类型¶
这些在方括号中接受类型参数的类型称为泛型类型或泛型,例如
您可以使用相同的内置类型作为泛型(带有方括号和内部的类型)
列表 (list)元组 (tuple)集合 (set)字典 (dict)
并且与之前的 Python 版本一样,从 typing 模块导入
联合 (Union)可选 (Optional)- ……等等。
在 Python 3.10 中,作为使用泛型 Union 和 Optional 的替代方法,您可以使用竖线 (|) 来声明类型的联合,这要好得多,也更简单。
您可以使用相同的内置类型作为泛型(带有方括号和内部的类型)
列表 (list)元组 (tuple)集合 (set)字典 (dict)
以及来自 typing 模块的泛型
联合 (Union)可选 (Optional)- ……等等。
类作为类型¶
您也可以将一个类声明为变量的类型。
假设您有一个类 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
信息
要了解更多关于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}"
Python 本身不会对这个 Annotated 做任何事情。对于编辑器和其他工具来说,类型仍然是 str。
但是,您可以使用 Annotated 中的这个空间来为FastAPI提供有关您希望应用程序如何行为的附加元数据。
要记住的关键是,您传递给 Annotated 的第一个类型参数是实际类型。其余的只是其他工具的元数据。
目前,您只需要知道 Annotated 存在,并且它是标准 Python。 😎
稍后您将看到它有多么强大。
提示
这个事实是标准 Python,意味着您仍然可以获得最佳的开发体验,在编辑器中,使用您分析和重构代码的工具等等。 ✨
而且,您的代码将与其他许多 Python 工具和库高度兼容。 🚀
FastAPI 中的类型提示¶
FastAPI 利用这些类型提示来执行多项操作。
使用FastAPI,您用类型提示声明参数,您将获得
- 编辑器支持.
- 类型检查.
……并且FastAPI使用相同的声明来
- 定义需求:来自请求的路径参数、查询参数、请求头、请求体、依赖项等。
- 转换数据:从请求到所需类型。
- 验证数据:来自每个请求
- 在数据无效时生成自动错误返回给客户端。
- 使用 OpenAPI记录 API
- 然后用于自动交互式文档用户界面。
这一切听起来可能很抽象。别担心。您将在教程 - 用户指南中看到所有这些实际应用。
重要的是,通过使用标准的 Python 类型,在一个地方(而不是添加更多类、装饰器等),FastAPI 将为您完成大量工作。
信息
如果您已经完成了所有教程并回来查看更多关于类型的内容,一个很好的资源是mypy 的“速查表”。