FastAPI 和 Pydantic 的未来一片光明
本文位于:
在很短的时间内
这是因为我们,作为 Python 社区的一员,都在定义着他们的未来。为了帮助我们自己,也为了帮助他人。从开发 Python 的核心开发人员,到本月开始学习 Python 的新开发人员。
只要这些工具能够帮助我们解决问题、帮助自己、帮助他人,并提高效率和生产力,我们就会让它们继续发挥作用并不断改进。
这就是我们都在做的事情。🤓🚀
简介
您可能不久前听说过 PEP 563、PEP 649 以及一些可能在未来影响 Pydantic 和 FastAPI 的变化。
如果你读过这篇文章,我并不指望你能理解其中的含义。我花了好几个小时阅读所有相关内容,并做了多次实验,才完全理解。
这可能会让您担心,甚至让您有点困惑。
现在没什么好担心的了。不过,我还是想在这里帮你澄清一下,并提供一些背景信息。
做好准备,您将进一步了解 Python 的工作原理、FastAPI 和 Pydantic 的工作原理、类型注释的工作原理等。👇
细节
从基本的 FastAPI 应用开始
FastAPI 基于 Pydantic。让我们来看一个同时使用它们的简单示例。
假设我们有一个./main.py
包含以下代码的文件:
from typing import Optional
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
if __name__ == "__main__":
uvicorn.run(app)
您可以运行此示例并使用以下命令启动 API 应用程序:
$ python ./main.py
INFO: Started server process [4418]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
然后,您可以打开浏览器并与 API 文档进行交互http://127.0.0.1:8000/docs
,等等。
但在这里我们想关注幕后发生的事情。
注意:除了使用最后两行之外,您还可以使用uvicorn
命令,这通常是您通常会做的。但对于本例来说,从命令的角度来查看所有内容会很有帮助python
。
Python 的工作原理
通过运行上述命令,您将要求系统启动名为的程序python
。并将文件main.py
作为参数提供给它。
注意:在 Windows 中,可能会调用该程序,python.exe
而不仅仅是python
。
python
那个名为(或)的程序python.exe
是用另一种名为“C”的编程语言编写的。也许你知道这一点。
该程序的作用python
是读取文件,使用Pythonmain.py
编程语言解释我们在其中编写的代码,并逐步执行。
因此,我们有两个东西或多或少都具有相同的名称“python”,但它们代表的东西略有不同:
python
:运行我们代码的程序(实际上是用C编程语言编写的)- “Python”:我们用来编写代码的编程语言的名称
所以,你可以说python
(该程序)可以读取Python(编程语言)。
什么是运行时
现在,当该程序python
执行我们用Python编程语言编写的代码时,我们称之为“运行时”。
这只是执行我们的代码的那段时间。
当我们的代码没有被执行的时候,比如说,当我们正在编辑文件的时候./main.py
,它就没有运行,所以我们就没有处于运行时。
该程序的工作方式是,在运行时(当我们的代码被执行时),Pydantic 和 FastAPI 读取这些类型注释(或类型提示)以提取它们的数据并对其进行操作。
例如,在Item
上面的类中,我们有:
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
在运行时,Pydantic 和 FastAPI 会识别出name
是str
并且price
是float
。如果我们发送一个 JSON 请求,其中包含 ,price
而 不是float
,它们将能够为我们验证数据。
FastAPI 和 Pydantic 都是用纯Python编写的。这些工具是如何做到这一点的呢?Python非常强大,它拥有实现这一点的功能,可以在运行时从相同的Python代码中读取类型注释。而 Pydantic 和 FastAPI 正是利用了这些功能。
另一个常用于指在运行时执行操作的术语是动态执行操作。
什么是静态分析
与运行时对应的是静态的。它只是意味着代码没有被执行。它只是被视为包含代码的文本文件。
在许多情况下,“静态”用于表示静态分析、静态检查、静态类型检查等。它指的是理解Python编程语言规则并可以分析代码但不能执行代码本身的工具。
这些静态分析工具可以检查代码是否正确遵循规则,检查代码是否有效,并提供自动补全等功能。当你在编辑代码时,编辑器中某处出现错误,并显示一条弯曲的红线,这就是静态分析。
在某些情况下,代码可能有效,但仍然不正确。例如,如果你尝试将 astr
和 afloat
相加:
name = "Rick"
price = 1.99
total = name + price
就语言本身的规则而言,该代码是有效的,所有引号都在它们应该在的位置,等号的位置正确,等等。但该代码仍然不正确并且无法工作,因为您不能将 astr
与 一起添加float
。
许多编辑器会显示一条非常有价值的红色波浪线,并在下方显示错误信息,name + price
这可能会节省你数小时的调试时间。这也是静态分析。
您可能听说过的一些进行静态分析的工具是:
mypy
,官方和主要的静态类型检查器flake8
,检查风格和正确性black
以一致的方式自动格式化代码,从而提高效率- PyCharm是最流行的 Python 编辑器之一,它具有内部组件,可以进行静态分析以检查错误、提供自动完成功能等。
- VS Code是另一个最流行的 Python 编辑器,它使用 Pylance,也有内部工具进行静态分析以检查错误、提供自动完成功能等。
这些工具通过在开发过程的早期阶段以及错误发生的确切位置检测出许多错误,节省了大量的开发时间。我敢打赌,在很多情况下,你可能已经看到了红线,意识到了错误是什么,然后想到“啊,是的,没错”,然后修复它,甚至几秒钟都没有意识到你的代码中有一个错误。如果我把这些工具帮我避免这些错误的次数都数一遍,我很快就会不知所措。😅
如果你曾经在之前没有类型注解的代码库中添加过类型注解,你可能会发现代码库中有很多不完善的部分和一些不合理的极端情况,这些情况会突然变得显而易见,然后你就可以修复它们。我肯定就遇到过这种情况。
Python 中的类型注释
我们在所有受支持的现代 Python 版本(Python 3.6 及以上版本)中提供的类型注释(也称为类型提示)旨在改进所有静态分析。
最初的意图是允许mypy
其他人在编写代码时为开发人员提供帮助。这在一段时间内是主要关注点。
但是后来,像dataclasses
(来自标准库)和Samuel Colvin的 Pydantic 这样的工具开始使用这些类型注释来执行不仅仅是静态分析 的操作,并且在运行时使用这些相同的类型注释。就 Pydantic 而言,提取这些信息来进行数据转换、验证和文档编制。
带有前向引用的类型注解
现在,假设我们有一个这样的类(它可能是一个 Pydantic 模型):
from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: str
child: Optional[Person] = None
这里我们有一个Person
,它可以有一个子代,它也是一个Person
。看起来一切都很好,对吧?
但是现在当我们运行代码时(或者借助编辑器中的一些静态分析),我们会看到我们child: Optional[Person]
在类主体内部声明了Person
。因此,当运行该部分代码时python
,Person
内部name: Optional[Person]
还不存在(该类仍在创建中)。
这被称为前向引用。它会导致代码崩溃。
再次强调,这些类型注解的主要目的是帮助进行静态分析。在运行时使用它们还不是一个重要的用例。
而仅仅因为我们试图改进静态分析而导致代码中断会非常烦人。
Person
为了解决这个问题,将内部声明为文字字符串也是有效的,如下所示:
from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: str
child: Optional["Person"] = None
我发现它的时候觉得很奇怪。它只是把一个类名放在一个字符串里而已。但它是有效的。
运行时python
,它会将其视为文字字符串,因此不会中断。
大多数静态分析工具都知道这是有效的,并且会读取文字字符串并理解它实际上指的是Person
类。
通过了解Optional["Person"]
实际上指的是Person
类,静态分析工具可以检测到这将是一个错误:
parent = Person(name="Beth")
parent.child = 3
智能编辑器将使用其静态分析工具来检测parent.child = 3
错误,因为它期望一个Person
。
这解决了代码中的前向引用问题,并且允许我们仍然可以使用静态分析工具。
...我们还没有讨论在运行时使用这些类型注释,但我们稍后会讨论。
Python 中的 PEP
PEP 是Python 增强提案 (Python Enhancement Proposal)的缩写。PEP 是一份技术文档,描述 Python 的变更、标准库的新增功能(例如,新增dataclasses
)以及其他类型的变更。在某些情况下,它们只是提供信息并建立约定。
名称为“提案”,但当它们最终被接受时,它们就成为标准。
PEP 563 - 注释的延迟评估
了解了什么是 PEP,让我们回到上面的代码示例。
如果你以前没见过类似的情况Optional["Person"]
,你可能会有点尴尬。我第一次发现这是真的时确实尴尬,但这也是可以理解的,因为它确实能解决问题。
然后Łukasz Langa有了一个聪明的想法并写下了PEP 563。
如果类型注释的解释方式发生了改变,并且如果Python隐式地理解它们就好像它们都只是字符串一样,那么我们就不必将所有这些类放在代码中奇怪位置的字符串中。
因此,我们可以像这样编写代码:
from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: str
child: Optional[Person] = None
然后,无论何时python
读取我们的文件,./main.py
它都会看到它好像是这样写的:
from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: "str"
child: "Optional[Person]" = None
因此,python
我们的代码可以顺利运行,不会中断。
而我们开发人员会更高兴,因为不需要记住在字符串中将东西放在哪里,也不用放在哪里。
即使在这些带有前向引用的类型注解中,我们也能继续使用自动补全和类型检查。例如,使用以前的技术在字符串中触发自动补全可能并不总是有效,但有了这项更改,这将不再是问题。
如果某些工具由于其他原因最终在运行时使用这些类型注释,那么仍然可以有办法在运行时获取信息,尽管有一些小警告,但仍然是可能的。
剧透警告:这些小警告后来会成为 Pydantic 的一个麻烦问题,但我们会解决的。
注意:请记住,这是几年前完成的,实际上,就在 Pydantic 首次发布的同一年。在运行时将类型注释用于静态分析以外的其他目的,这根本不是一个常见的用例。令人惊讶的是,它甚至被考虑到了。
现在,由于这会在某种程度上彻底改变 Python 的内部行为,因此默认情况下不会强制执行。相反,它可以通过特殊的导入方式来实现from __future__ import annotations
:
from __future__ import annotations
from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: str
child: Optional[Person] = None
现在这些类型注释仅被视为字符串,因此在仅将它们用于静态分析时可以使用一些有趣的技巧,例如在以前的版本中使用 Python 未来版本的类型功能。
例如,即使在 Python 3.7 中,声明Person | None
而不是Optional[Person]
,也要避免额外的Optional
和额外的导入(该功能在 Python 3.10 中可用,但在 Python 3.7 中不可用):
from __future__ import annotations
class Person:
name: str
child: Person | None = None
注意:请记住,这只适用于静态分析工具,即使在 Python 3.7 中,您的编辑器也可以理解这一点,但 Pydantic 无法使用它并且无法正常工作。
这个功能从 Python 3.7 开始就存在了。并且这个功能计划在 Python 3.10 及以后成为默认设置(现在还不行,但请继续阅读)。
Pydantic 和 PEP 563
现在,时间快进到几个月前。
from __future__ import annotations
由于 PEP 563 的支持,Pydantic 已经提供了一些在代码中使用此功能的支持。而且在很多情况下,它都能正常工作。例如,下面的代码可以正常工作:
from __future__ import annotations
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
# ✅ Pydantic models outside of functions will always work
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
但有些警告不起作用。例如,下面这个不起作用:
from __future__ import annotations
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
def create_app():
# 🚨 Pydantic models INSIDE of functions would not work
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
return app
app = create_app()
如果你运行该代码,你会得到一个令人不安的错误:
NameError: name 'Item' is not defined
为了解决这个问题,你可以将Item
类移到函数之外。还有其他一些类似的极端情况。
这些令人不安的问题对于 Python 新手来说尤其不方便(对于许多有经验的 Python 开发人员来说可能也是如此),因为对于不了解内部结构的人来说,这个问题根本不明显(对我来说并不明显,而我构建了 FastAPI 和 Typer 😅)。
Python 是一个非常包容的全球科技社区的典范,它欢迎来自世界各地、各个学科的新人。它被用于解决最复杂的问题,包括拍摄黑洞照片、在火星上运行无人机以及构建最复杂的人工智能系统。但与此同时,由于其易用性和简洁性,它也是许多人的第一编程语言。许多 Python 开发者甚至不认为自己是“开发者”,即使他们用它来解决问题。
因此,默认存在这样的不便并非理想情况。还有其他一些注意事项,但我不想深入探讨已经了解的技术细节。您可以在Pydantic 问题、邮件列表主题以及Łukasz 的详细解释中阅读更多相关信息。
PEP 649 - 使用描述符延迟计算注释
最近,一直在研究 PEP 563、PEP 649替代方案的 Larry Hastings 在Brett Cannon (来自 Python 指导委员会)的建议下,联系了 Samuel Colvin(Pydantic 的作者)和我(FastAPI 和 Typer 的作者),看看这些变化是否以及如何影响我们。
我们意识到 PEP 563(另一个)的更改将永久添加到 Python 3.10(不需要from __future__ import annotations
),并且警告和问题仍然没有解决方案。
突然间,我们也清楚地认识到,这些在运行时使用类型注释而不是仅用于静态分析的用例对于所有参与者来说并不是一个明显的用例,包括正在研究这些用例的潜在解决方案的 Larry Hastings。
请求重新考虑
遗憾的是,我们意识到这一切太晚了,距离这些变化在 Python 3.10 中正式生效只有几周时间(最终并没有)。尽管如此,我们还是表达了我们的担忧。
如果你之前读过这篇文章,可能就是这个原因。它被分享了很多次,而且有点失控了。
可悲的是,有一些激进的评论攻击了其中涉及的几个部分(Python 指导委员会、我们等等),就好像这是一场不同群体之间的争斗。😕
实际上,我们只是一个大团体,即 Python 社区,我们都在努力为所有人做到最好。
遗憾的是,所有这些突如其来的摩擦给所有相关方带来了巨大的压力。对于 Python 指导委员会、核心 Python 开发人员以及我们这些库作者来说,都是如此。
幸好最后一切都好起来了。
在此向Carol Willing致以最崇高的敬意。尽管这给她自己和其他所有相关人员带来了额外的压力,但她在协调不同观点、减少摩擦、平息事态方面做出了巨大贡献。这种承认并采纳他人观点的能力弥足珍贵。我们需要更多像 Carol Willings 这样的人。🤓
Python 指导委员会决议
如果你不知道的话, Python 指导委员会将决定哪些内容可以纳入 Python,哪些不可以。
目前其组成人员为:
- 巴里·华沙
- 布雷特·坎农
- 卡罗尔·威林
- 巴勃罗·加林多·萨尔加多
- 托马斯·沃特斯
现在,回到故事,经过几天的先前讨论后,在下一次 Python 指导委员会会议期间,他们一致决定撤销将这些类型注释作为字符串(如 PEP 563 中所述)作为默认行为的决定。
在 Python 3.10 中默认使用这些字符串类型注释已经是很久以前的事情了,而在“功能冻结”(下一个版本不再接受任何更改的时刻)前几周才撤销这一更改是一个重大决定,需要付出很多额外的压力和努力。
尽管如此,他们还是做出了这个决定,以便支持使用以下功能的 FastAPI、Pydantic 和其他库的用户社区:
我们不能冒险损害 FastAPI/pydantic 用户的一小部分,更不用说我们尚不知道的评估类型注释的其他用途。
这再次表明了 Python 社区从指导委员会开始的坚定承诺,即包容并支持所有用户,无论其使用情况如何。
这里再次向Pablo Galindo致以最崇高的敬意,他付出了额外的努力来完成所有最后一刻的改变,甚至还投票赞成。
下一步
决定是保留当前行为,即允许from __future__ import annotations
在代码中使用,如 PEP 563 所定义,但不是默认行为。
这将提供足够的时间来找到适用于所有用例的解决方案或替代方案,包括 Pydantic、FastAPI 以及专门对静态分析感兴趣的用例。
这对于每个人来说都是最好的结果。🎉
它提供了足够的时间来寻找替代解决方案,并避免了在短时间内做出可能产生未知负面影响的匆忙决定。
谁关心 FastAPI 和 Pydantic
现在,总的来说,FastAPI 和 Pydantic 的未来会怎样?谁会关心它们呢?
使用 Pydantic 的 FastAPI 首次入选上一期 Python 开发者调查。尽管是第一次入选,但它已经位列第三大最受欢迎的 Web 框架,仅次于 Flask 和 Django。这表明它对许多人来说都很有用。
它还被列入最新的ThoughtWorks 技术雷达,成为企业应该开始尝试的技术之一。
FastAPI 和 Pydantic 目前正被许多产品和组织使用,从您听说过的最大的团队到最小的团队,包括单独的开发人员。
一些流行且广泛使用的云提供商、SaaS 工具、数据库等正在添加文档、教程,甚至改进其服务,以更好地服务于 FastAPI 用户。
最受欢迎的 Python 代码编辑器PyCharm和Visual Studio Code一直在努力改进对 FastAPI 和 Pydantic 的支持。我甚至直接与这两个团队进行了沟通。🤓
这尤其令人感兴趣,因为 FastAPI 的设计初衷是获得编辑器的最佳支持,从而提供尽可能最佳的开发者体验。FastAPI 和 Pydantic 几乎只使用了该语言的标准功能。当编辑器改进(甚至更多)对这些工具的支持时,他们实际上是在改进对该语言本身功能的支持。除了 FastAPI 和 Pydantic 之外,这还使许多其他用例受益。
结论
Python 是一个伟大的社区。
我们都在努力让它变得更好,从指导委员会和核心开发人员到图书馆作者,甚至是那些帮助其他人使用这些图书馆的人。
FastAPI 和 Pydantic 是这个社区的一部分,它包含并支持所有人及其所有用例。
这就是 FastAPI 和 Pydantic 未来如此光明的主要原因。因为 Python 的未来是光明的。我们共同创造这个未来。✨
谢谢
感谢所有参与寻找解决方案和改进 Python 社区的人。🙇
特别感谢:
在发表之前对本文进行审阅和反馈。
关于我
嘿! 👋 我是塞巴斯蒂安·拉米雷斯 ( tiangolo )。
您可以关注我、联系我、了解我所做的事情,或者使用我的开源代码:
文章来源:https://dev.to/tiangolo/the-future-of-fastapi-and-pydantic-is-bright-3pbm