Python 3.11 测试版
Python 的新版本于每年 10 月发布。该代码是经过开发和测试的十七个月期间在发布日期之前。新功能在α相。对于 Python 3.11,七个阿尔法版本是在 2021 年 10 月至 2022 年 4 月期间制作的。
首先测试版Python 3.11 的发布发生在凌晨2022 年 5 月 8 日。每个此类预发布均由发布经理协调 — 目前巴勃罗·加林多·萨尔加多——并将来自 Python 核心开发人员和其他志愿者的数百个提交联系在一起。
此次发布还标记的这功能冻结对于新版本。换句话说,Python 3.11 中不会添加 Python 3.11.0b1 中尚未存在的新功能。相反,功能冻结和发布日期之间的时间(2022 年 10 月 3 日)用于测试和固化代码。
关于每月一次在此期间贝塔期,Python核心开发者发布新测试版继续展示新功能、测试它们并获得早期反馈。目前,Python 3.11的最新测试版是3.11.0b3,发布于2022 年 6 月 1 日.
笔记:本教程使用Python 3.11的第三个测试版。如果您使用更高版本,您可能会遇到细微的差异。然而,tomllib
构建在成熟的库上,您可以预期您在本教程中学到的内容在整个测试阶段和 Python 3.11 的最终版本中都将保持不变。
如果您正在维护自己的 Python 包,那么测试阶段是一个重要的时期,您应该开始使用新版本测试您的包。核心开发人员希望与社区一起在最终版本发布之前找到并修复尽可能多的错误。
很酷的新功能
Python 3.11 的一些亮点包括:
-
增强的错误消息,这可以帮助您更有效地调试代码
-
任务组和异常组,它简化了异步代码的使用并允许程序引发和处理多重异常同时
-
TOML支持,它允许您使用标准库解析 TOML 文档
-
静态类型改进,这让您可以更精确地注释代码
-
优化,这有望使 Python 3.11 比以前的版本快得多
Python 3.11 有很多值得期待的地方!您已经可以阅读有关增强的错误消息和任务组和异常组在早些时候Python 3.11 预览文章。如需全面概述,请查看Python 3.11:值得您尝试的酷炫新功能.
在本教程中,您将重点关注如何使用新的tomllib
用于读取和解析 TOML 文件的库。您还将简要了解 Python 3.11 附带的一些类型改进。
安装
要使用本教程中的代码示例,您需要在系统上安装 Python 3.11 版本。在本小节中,您将了解执行此操作的几种不同方法:使用码头工人, 使用pyenv,或从安装来源。选择最适合您和您的系统的一种。
笔记:Beta 版本是即将推出的功能的预览。虽然大多数功能都可以正常运行,但您不应该在生产环境或任何其他潜在错误会造成严重后果的地方依赖任何 Python 3.11 beta 版本。
如果您有权访问码头工人在您的系统上,然后您可以通过拉取并运行来下载最新版本的Python 3.11python:3.11-rc-slim
Docker镜像:
$ docker pull python:3.11-rc-slim
3.11-rc-slim: Pulling from library/python
[...]
docker.io/library/python:3.11-rc-slim
$ docker run -it --rm python:3.11-rc-slim
这会让你进入 Python 3.11 REPL。查看在 Docker 中运行 Python 版本有关通过 Docker 使用 Python 的更多信息,包括如何运行脚本。
这pyenv该工具非常适合管理系统上不同版本的 Python,如果您愿意,可以使用它来安装 Python 3.11 beta。它有两个不同的版本,一种适用于 Windows,另一种适用于 Linux 和 macOS。使用下面的切换器选择您的平台:
在 Windows 上,您可以使用pyenv-win。首先更新你的pyenv
安装:
PS> pyenv update
:: [Info] :: Mirror: https://www.python.org/ftp/python
[...]
进行更新可确保您可以安装最新版本的 Python。你也可以手动更新 pyenv.
在 Linux 和 macOS 上,您可以使用pyenv。首先更新你的pyenv
安装,使用pyenv 更新插入:
$ pyenv update
Updating /home/realpython/.pyenv...
[...]
进行更新可确保您可以安装最新版本的 Python。如果您不想使用更新插件,那么您可以手动更新 pyenv.
使用pyenv install --list
检查哪些版本的 Python 3.11 可用。然后,安装最新的:
$ pyenv install 3.11.0b3
Downloading Python-3.11.0b3.tar.xz...
[...]
安装可能需要几分钟时间。安装新的测试版后,您可以创建一个虚拟环境你可以在哪里玩它:
PS> pyenv local 3.11.0b3
PS> python --version
Python 3.11.0b3
PS> python -m venv venv
PS> venv\Scripts\activate
你用pyenv local
激活你的Python 3.11版本,然后设置虚拟环境python -m venv
.
$ pyenv virtualenv 3.11.0b3 311_preview
$ pyenv activate 311_preview
(311_preview) $ python --version
Python 3.11.0b3
在 Linux 和 macOS 上,您可以使用pyenv-virtualenv用于设置虚拟环境并激活它的插件。
您还可以从以下网站上提供的预发行版本之一安装 Pythonpython.org。选择最新预发布并向下滚动到文件页面底部的部分。下载并安装与您的系统相对应的文件。看了解更多信息。
本教程中的大多数示例都依赖于新功能,因此您应该使用 Python 3.11 可执行文件运行它们。运行可执行文件的具体方式取决于您的安装方式。如果您需要帮助,请查看相关教程码头工人, pyenv, 虚拟环境, 或者从源安装.
tomllib
Python 3.11 中的 TOML 解析器
Python 是一门成熟的语言。 Python 的第一个公共版本于 1991 年发布,距今已有三十多年了。许多 Python 的独特功能,包括显式异常处理、对空格的依赖以及丰富的数据结构(如列表和字典),甚至在早些年.
不过,Python 第一个版本中缺少的一个功能是共享社区包和模块的便捷方式。这并不奇怪。事实上,Python 大约是在同一时间被发明的。全球资讯网。 1991年底,仅存在十二个网络服务器在全球范围内,他们都没有致力于分发 Python 代码。
随着时间的推移,双方Python和互联网变得更受欢迎。一些倡议旨在允许共享 Python 代码。这些特性有机地演变并导致了 Python 的某种程度的发展。混乱的与包装的关系。
这个问题已经通过几个解决方案得到解决包装 PEP(Python 增强提案)在过去的几十年里,情况已经有了很大的改善图书馆维护者和终端用户.
一个挑战是构建包依赖于执行setup.py
文件,但没有机制可以知道该文件依赖哪些依赖项。这就创造了一种先有鸡还是先有蛋你需要运行的问题setup.py
发现如何跑步setup.py
.
在实践中,pip—Python 的包管理器 — 假设它应该使用安装工具构建软件包并且安装工具在您的计算机上可用。这使得使用替代构建系统变得更加困难,例如掠过和诗歌.
为了解决这个问题,公众号 518介绍了pyproject.toml
配置文件,指定 Python 项目构建依赖项。 PEP 518 于 2016 年被接受。当时,TOML 仍然是一种相当新的格式,并且没有内置支持在 Python 或其标准库中解析 TOML。
随着 TOML 格式的成熟和使用pyproject.toml
文件已经解决,Python 3.11 添加了对解析 TOML 文件的支持。在本节中,您将详细了解 TOML 格式是什么以及如何使用新的tomllib
解析 TOML 文档,以及为什么tomllib
不支持写入TOML文件。
学习基本 TOML
汤姆·普雷斯顿-维尔纳第一的宣布 汤姆的浅显易懂的语言——俗称TOML——并发布版本0.1.02013 年制定了其规范。从一开始,TOML 的目标就是提供一种“由于语义明显而易于阅读的最小配置文件格式”(来源)。稳定的版本1.0.0TOML 规范于 2021 年 1 月发布。
TOML 文件是UTF-8编码的、区分大小写的文本文件。 TOML 中的主要构建块是键值对,其中键与值之间用等号分隔(=
):
在这个最小的 TOML 文档中,version
是一个具有对应值的键3.11
。 TOML 中的值具有类型。3.11
被解释为浮点数。您可以利用的其他基本类型是字符串, 布尔值, 整数,和日期:
version = 3.11
release_manager = "Pablo Galindo Salgado"
is_beta = true
beta_release = 3
release_date = 2022-06-01
此示例显示了大多数此类类型。除了具有小写布尔值和特殊的日期文字之外,其语法与 Python 的语法类似。 TOML 键值对的基本形式类似于 Python 变量赋值,因此它们应该看起来很熟悉。有关这些和其他相似之处的更多详细信息,请查看TOML文档.
TOML 文档的核心是键值对的集合。您可以通过将它们包装在数组和表中来向这些对添加一些结构。一个大批是一个值列表,类似于 Python列表。 A桌子是一个嵌套的键值对集合,类似于Pythondict
.
您可以使用方括号来包裹数组的元素。一个表是通过以下方式启动的:[key]
命名表的行:
[python]
version = 3.11
release_manager = "Pablo Galindo Salgado"
is_beta = true
beta_release = 3
release_date = 2022-06-01
peps = [657, 654, 678, 680, 673, 675, 646, 659]
[toml]
version = 1.0
release_date = 2021-01-12
该 TOML 文档可以用 Python 表示如下:
{
"python": {
"version": 3.11,
"release_manager": "Pablo Galindo Salgado",
"is_beta": True,
"beta_release": 3,
"release_date": datetime.date(2022, 6, 1),
"peps": [657, 654, 678, 680, 673, 675, 646, 659],
},
"toml": {
"version": 1.0,
"release_date": datetime.date(2021, 1, 12),
},
}
这[python]
TOML 中的 key 在 Python 中由 a 表示"python"
字典中的 key 指向包含 TOML 部分中所有键值对的嵌套字典。 TOML表可以任意嵌套,一个TOML文档可以包含多个TOML表。
TOML 语法的简短介绍到此结束。尽管 TOML 在设计上具有相当简单的语法,但仍有一些细节在此未涵盖。要深入了解,请查看Python 和 TOML:新的好朋友或者TOML规范.
除了语法之外,您还应该考虑如何解释 TOML 文件中的值。 TOML文档通常用于配置。最终,其他一些应用程序会使用 TOML 文档中的信息。因此,该应用程序对 TOML 文件的内容有一些期望。这意味着 TOML 文档可能有两种不同类型的错误:
-
语法错误:TOML 文档不是有效的 TOML。 TOML 解析器通常会捕获这一点。
-
架构错误:TOML 文档是有效的 TOML,但其结构不是应用程序所期望的。应用程序本身必须处理这个问题。
TOML 规范目前不包括可用于验证 TOML 文档结构的模式语言,尽管有几种提案存在。这样的模式将检查给定的 TOML 文档是否包含给定用例的正确表、键和值类型。
作为非正式模式的示例,公众号 517和公众号 518说一个pyproject.toml
文件应定义build-system
表,其中必须包含键requires
和build-backend
。此外,价值requires
必须是字符串数组,而值build-backend
必须是一个字符串。下面是一个例子满足此模式的 TOML 文档:
# pyproject.toml
[build-system]
requires = ["setuptools>=61.0.0", "wheel"]
build-backend = "setuptools.build_meta"
此示例遵循 PEP 517 和 PEP 518 的要求。但是,该验证通常由构建前端完成。
笔记:如果您想了解有关使用 Python 构建自己的包的更多信息,请查看如何将开源 Python 包发布到 PyPI.
您可以自己检查此验证。创建以下错误pyproject.toml
文件:
# pyproject.toml
[build-system]
requires = "setuptools>=61.0.0"
backend = "setuptools.build_meta"
这是有效的 TOML,因此任何 TOML 解析器都可以读取该文件。然而,这不是一个有效的build-system
根据 PEP 中的要求填写表格。要确认这一点,请安装建造,这是一个符合 PEP 517 的构建前端,并根据您的pyproject.toml
文件:
(venv) $ python -m pip install build
(venv) $ python -m build
ERROR Failed to validate `build-system` in pyproject.toml:
`requires` must be an array of strings
错误消息指出requires
必须是一个字符串数组,如 PEP 518 中所指定。使用您的其他版本pyproject.toml
归档并记下哪些其他验证build
为你做。您可能需要在自己的应用程序中实现类似的验证。
到目前为止,您已经看到了一些 TOML 文档的示例,但是您还没有探索如何在自己的项目中使用它们。在下一小节中,您将了解如何使用新的tomllib
封装在标准库中以读取和解析Python 3.11中的TOML文件。
阅读 TOML 的方式tomllib
Python 3.11 在标准库中附带了一个新模块,名为托姆利库。您可以使用tomllib
读取和解析任何 TOML v1.0 兼容文档。在本小节中,您将了解如何直接从文件和包含 TOML 文档的字符串加载 TOML。
PEP 680描述tomllib
以及导致 TOML 支持被添加到标准库中的一些过程。纳入的两个决定因素tomllib
Python 3.11 中的核心角色是pyproject.toml
在 Python 打包生态系统中发挥作用,TOML 规范将于 2021 年初达到 1.0 版。
实施tomllib
或多或少是直接从通利经过塔内利·胡基宁,他也是 PEP 680 的合著者之一。
这tomllib
模块非常简单,只包含两个功能:
-
load()
从文件中读取 TOML 文档。
-
loads()
从字符串读取 TOML 文档。
您将首先看到如何使用tomllib
阅读以下内容pyproject.toml
文件,它是同一文件的简化版本通利项目:
# pyproject.toml
[build-system]
requires = ["flit_core>=3.2.0,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "tomli"
version = "2.0.1" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version DO IT
description = "A lil' TOML parser"
requires-python = ">=3.7"
readme = "README.md"
keywords = ["toml"]
[project.urls]
"Homepage" = "https://github.com/hukkin/tomli"
"PyPI" = "https://pypi.org/project/tomli"
复制此文档并将其保存在名为pyproject.toml
在您的本地文件系统上。您现在可以启动 REPL 会话来探索 Python 3.11 的 TOML 支持:
>>>>>> import tomllib
>>> with open("pyproject.toml", mode="rb") as fp:
... tomllib.load(fp)
...
{'build-system': {'requires': ['flit_core>=3.2.0,<4'],
'build-backend': 'flit_core.buildapi'},
'project': {'name': 'tomli',
'version': '2.0.1',
'description': "A lil' TOML parser",
'requires-python': '>=3.7',
'readme': 'README.md',
'keywords': ['toml'],
'urls': {'Homepage': 'https://github.com/hukkin/tomli',
'PyPI': 'https://pypi.org/project/tomli'}}}
你用load()
通过将文件指针传递给函数来读取和解析 TOML 文件。请注意,文件指针必须指向二进制流。确保这一点的一种方法是使用open()
和mode="rb"
,其中b
表示二进制模式。
笔记:根据PEP 680,文件必须以二进制模式打开,以便tomllib
可以确保 UTF-8 编码在所有系统上都能正确处理。
将原始 TOML 文档与生成的 Python 数据结构进行比较。该文档由 Python 字典表示,其中所有键都是字符串,TOML 中的不同表表示为嵌套字典。观察评论关于version
原始文件中的内容将被忽略,并且不属于结果的一部分。
您可以使用loads()
加载已在字符串中表示的 TOML 文档。以下示例解析来自上一小节:
>>>>>> import tomllib
>>> document = """
... [python]
... version = 3.11
... release_manager = "Pablo Galindo Salgado"
... is_beta = true
... beta_release = 3
... release_date = 2022-06-01
... peps = [657, 654, 678, 680, 673, 675, 646, 659]
...
... [toml]
... version = 1.0
... release_date = 2021-01-12
... """
>>> tomllib.loads(document)
{'python': {'version': 3.11,
'release_manager': 'Pablo Galindo Salgado',
'is_beta': True,
'beta_release': 3,
'release_date': datetime.date(2022, 6, 1),
'peps': [657, 654, 678, 680, 673, 675, 646, 659]},
'toml': {'version': 1.0,
'release_date': datetime.date(2021, 1, 12)}}
类似于load()
, loads()
返回一个字典。一般来说,表示基于基本的 Python 类型:str
, float
, int
, bool
, 也字典, 列表, 和日期时间对象。这tomllib
文档包括换算表显示了 TOML 类型在 Python 中的表示方式。
如果您愿意,那么您可以使用loads()
通过将其与以下内容结合来从文件中读取 TOMLpathlib
:
>>>>>> import pathlib
>>> import tomllib
>>> path = pathlib.Path("pyproject.toml")
>>> with path.open(mode="rb") as fp:
... from_load = tomllib.load(fp)
...
>>> from_loads = tomllib.loads(path.read_text())
>>> from_load == from_loads
True
在此示例中,您加载pyproject.toml
两者同时使用load()
和loads()
。然后,您确认无论您如何加载文件,Python 表示形式都是相同的。
两个都load()
和loads()
接受一个可选参数:解析浮点。这使您可以控制如何浮点数字是解析和表示在Python中。默认情况下,它们被解析并存储为float
对象,在大多数 Python 实现中都是 64 位,大约有 16 位十进制数字精确.
如果您需要使用更精确的数字,一种替代方法是使用小数.小数反而:
>>>>>> import tomllib
>>> from decimal import Decimal
>>> document = """
... small = 0.12345678901234567890
... large = 9999.12345678901234567890
... """
>>> tomllib.loads(document)
{'small': 0.12345678901234568,
'large': 9999.123456789011}
>>> tomllib.loads(document, parse_float=Decimal)
{'small': Decimal('0.12345678901234567890'),
'large': Decimal('9999.12345678901234567890')}
在这里,您加载带有两个键值对的 TOML 文档。默认情况下,使用时会失去一点精度load()
或者loads()
。通过使用Decimal
类,您可以保持输入的精度。
如前所述,tomllib
模块改编自流行的tomli
模块。如果您想使用 TOML 并且tomllib
在需要支持旧版本 Python 的代码库上,那么您可以依靠tomli
。为此,请在您的需求文件中添加以下行:
tomli >= 1.1.0 ; python_version < "3.11"
这将安装tomli
在 3.11 之前的 Python 版本上使用时。在您的源代码中,您可以使用tomllib
或者tomli
酌情与以下进口:
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib
此代码将导入tomllib
在 Python 3.11 及更高版本上。如果tomllib
不可用,那么tomli
被导入并别名为tomllib
姓名。
您已经了解了如何使用tomllib
阅读 TOML 文档。您可能想知道如何编写 TOML 文件。事实证明你不能用以下方式编写 TOMLtomllib
。请继续阅读以了解原因并查看一些替代方案。
写入TOML
类似的现有库如json
和pickle
包括两者load()
和dump()
函数,后者用于写入数据。这dump()
函数,以及相应的dumps()
,被故意排除在外tomllib
.
根据PEP 680以及围绕它的讨论,这样做有几个原因:
-
纳入的主要动机tomllib
在标准库中是能够读生态系统中使用的 TOML 文件。
-
TOML格式被设计成一种人性化的配置格式,因此很多TOML文件都是手动编写的。
-
TOML 格式并不是像 JSON 或 pickle 这样的数据序列化格式,因此与json
和pickle
API 不是必需的。
-
TOML 文档可能包含写入文件时应保留的注释和格式。这与将 TOML 表示为基本 Python 类型不兼容。
-
关于如何布局和格式化 TOML 文件有不同的看法。
-
没有一个核心开发人员表示有兴趣维护一个写入 APItomllib
.
一旦将某些内容添加到标准库中,就很难更改或删除,因为有人依赖它。这是一件好事,因为这意味着 Python 基本上保持向后兼容:在 Python 3.10 上运行的 Python 程序很少会在 Python 3.11 上停止工作。
另一个后果是核心团队对于添加新功能持保守态度。如果明确确实有需求,可以稍后添加对编写 TOML 文档的支持。
不过,这并不会让您空手而归。有多种可用的第三方 TOML 编写器。这tomllib
文档提到了两个包:
-
汤姆利-W顾名思义,是
tomli
可以编写TOML文档。这是一个简单的模块,没有很多控制输出的选项。
-
汤姆基特是一个用于处理 TOML 文档的强大包,它支持读取和写入。它保留注释、缩进和其他空白。 TOML Kit 的开发和使用是诗歌.
根据您的用例,这些包之一可能会满足您的 TOML 编写需求。
如果您不想添加外部依赖项只是为了编写 TOML 文件,那么您也可以尝试使用自己的编写器。以下示例显示了不完整的 TOML 编写器的示例。它不支持 TOML v1.0 的所有功能,但它支持足以编写pyproject.toml
您之前看到的示例:
# tomllib_w.py
from datetime import date
def dumps(toml_dict, table=""):
document = []
for key, value in toml_dict.items():
match value:
case dict():
table_key = f"{table}.{key}" if table else key
document.append(
f"\n[{table_key}]\n{dumps(value, table=table_key)}"
)
case _:
document.append(f"{key} = {_dumps_value(value)}")
return "\n".join(document)
def _dumps_value(value):
match value:
case bool():
return "true" if value else "false"
case float() | int():
return str(value)
case str():
return f'"{value}"'
case date():
return value.isoformat()
case list():
return f"[{', '.join(_dumps_value(v) for v in value)}]"
case _:
raise TypeError(
f"{type(value).__name__} {value!r} is not supported"
)
这dumps()
函数接受表示 TOML 文档的字典。它通过循环字典中的键值对将字典转换为字符串。您很快就会仔细查看详细信息。首先,您应该检查代码是否有效。打开 REPL 并导入dumps()
:
>>>>>> from tomllib_w import dumps
>>> print(dumps({"version": 3.11, "module": "tomllib_w", "stdlib": False}))
version = 3.11
module = "tomllib_w"
stdlib = false
您编写一个包含不同类型值的简单字典。它们被正确地编写为 TOML 类型:数字是普通的,字符串用双引号括起来,布尔值是小写的。
回头看一下代码。大多数 TOML 类型的序列化发生在辅助函数中,_dumps_value()
。它用结构模式匹配根据类型构造不同类型的 TOML 字符串value
.
主要的dumps()
函数与字典一起使用。它循环遍历每个键值对。如果该值是另一个字典,那么它会通过添加表头来构造一个 TOML 表,然后递归调用自身来处理表内的键值对。如果该值不是字典,则_dumps_value()
用于将键值对正确转换为 TOML。
如前所述,作者不支持完整的 TOML 规范。例如,它不支持 TOML 中提供的所有日期和时间类型,或内联表或数组表等嵌套结构。字符串处理中还存在一些不受支持的边缘情况。然而,这对于许多应用程序来说已经足够了。
例如,您可以尝试加载然后转储pyproject.toml
您之前使用过的文件:
>>>>>> import tomllib
>>> from tomllib_w import dumps
>>> with open("pyproject.toml", mode="rb") as fp:
... pyproject = tomllib.load(fp)
...
>>> print(dumps(pyproject))
[build-system]
requires = ["flit_core>=3.2.0,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
requires-python = ">=3.7"
readme = "README.md"
keywords = ["toml"]
[project.urls]
Homepage = "https://github.com/hukkin/tomli"
PyPI = "https://pypi.org/project/tomli"
在这里,您首先阅读pyproject.toml
和tomllib
。然后你用你自己的tomllib_w
将 TOML 文档写回控制台的模块。
您可以扩展tomllib_w
如果您需要更好的支持来编写 TOML 文档。但是,在大多数情况下,您应该依赖现有的软件包之一,例如tomli_w
或者tomlkit
, 反而。
虽然您不支持在 Python 3.11 中编写 TOML 文件,但附带的 TOML 解析器对于许多项目都非常有用。展望未来,您可以将 TOML 用于您的配置文件,因为您将获得在 Python 中读取它们的一流支持。
其他新功能
TOML 支持当然值得庆祝,但 Python 3.11 中也出现了一些较小的改进。长期以来发生这种增量变化的一个领域是 Python类型检查景观。
公众号 484引入了类型提示。它们从 Pyhon 3.5 以及每个新的 Python 版本开始可用增加功能到静态类型系统。乌卡斯·兰加在他的文章中谈到了类型检查基调在PyCon 美国 2022会议。
Python 3.11 接受了几个新的与类型相关的 PEP。您很快就会了解更多有关Self
类型,即LiteralString
类型和可变泛型。
笔记:类型检查增强功能有点特殊,因为它们取决于您的 Python 版本和类型检查工具的版本。最新的 Beta 版本支持一些新的 Python 3.11 类型系统功能,但尚未在所有类型检查器中实现。
例如,您可以监控以下状态:迈皮的支持其新功能GitHub 页面.
甚至还有一些与打字相关的新功能,下面不会介绍。公众号 681添加了@dataclass_transform
装饰者,它可以用类似于以下的语义来标记类数据类。此外,PEP 655让您可以标记必填和可选字段类型化字典.
自体型
公众号 673介绍了一个新的自我型动态引用当前类。当您使用返回类实例的方法实现类时,这非常有用。考虑以下二维点的部分实现:极坐标:
# polar_point.py
import math
from dataclasses import dataclass
@dataclass
class PolarPoint:
r: float
φ: float
@classmethod
def from_xy(cls, x, y):
return cls(r=math.hypot(x, y), φ=math.atan2(y, x))
您添加.from_xy()
构造函数,以便您可以方便地创建PolarPoint
来自相应的实例笛卡尔坐标.
笔记:属性名称.r
和.φ
被故意选择为类似于中使用的数学符号公式.
一般来说,是受到推崇的为您的属性使用更长、更具描述性的名称。但是,有时遵循问题域的约定也很有用。随意更换.r
和.radius
和.φ
和.phi
或者.angle
如果你更喜欢。
Python源代码被编码为UTF-8经过默认。仍然,身份标识比如变量和属性不能使用完整的 Unicode 字母表。例如,您必须远离从表情符号在变量和属性名称中。
您可以按如下方式使用新类:
>>>>>> from polar_point import PolarPoint
>>> point = PolarPoint.from_xy(3, 4)
>>> point
PolarPoint(r=5.0, φ=0.9272952180016122)
>>> from math import cos
>>> point.r * cos(point.φ)
3.0000000000000004
在这里,您首先创建一个代表笛卡尔点 (3, 4) 的点。在极坐标中,该点用半径表示r
= 5.0 和角度φ
≈ 0.927。您可以转换回笛卡尔坐标系x
与公式协调x = r * cos(φ)
.
现在,您想要添加类型提示.from_xy()
。它返回一个PolarPoint
目的。但是,您不能直接使用PolarPoint
此时作为注释,因为该类尚未完全定义。相反,您可以使用"PolarPoint"
带引号或添加公众号 563未来进口推迟评估的注释。
这两种解决方法都有其作用缺点,以及当前推荐是使用一个TypeVar
反而。这种方法即使在子类中也可以工作,但它很麻烦并且容易出错。
随着新Self
类型,您可以将类型提示添加到您的类中,如下所示:
import math
from dataclasses import dataclass
from typing import Self
@dataclass
class PolarPoint:
r: float
φ: float
@classmethod
def from_xy(cls, x: float, y: float) -> Self:
return cls(r=math.hypot(x, y), φ=math.atan2(y, x))
注释-> Self
表明.from_xy()
将返回当前类的实例。如果您创建一个子类,这也将正常工作PolarPoint
.
拥有Self
在工具箱中键入将使使用类和面向对象的功能(如继承)向项目添加静态类型变得更加方便。
任意文字字符串类型
Python 3.11 带来的另一种新类型是LiteralString
。虽然这个名字可能会让你想起Literal
,它是在 Python 3.8 中添加的,主要用例LiteralString
有点不同。要理解将其添加到类型系统的动机,首先退后一步考虑一下字符串。
一般来说,Python 并不关心你如何构造字符串:
>>>>>> s1 = "Python"
>>> s2 = "".join(["P", "y", "t", "h", "o", "n"])
>>> s3 = input()
Python
>>> s1 == s2 == s3
True
在此示例中,您创建字符串"Python"
以三种不同的方式。首先,将其指定为文字字符串。接下来,连接六个单字符字符串的列表以形成字符串"Python"
。最后,你读取字符串从用户输入使用输入().
最终测试表明每个字符串的值是相同的。在大多数应用程序中,您不需要关心特定字符串是如何构造的。但是,有时您需要小心,特别是在处理用户输入时。
SQL注入不幸的是,针对数据库的攻击很常见。这Java Log4j 漏洞类似地利用日志系统执行任意代码。
回到上面的例子。虽然价值观s1
和s3
碰巧是相同的,你对这两个字符串的信任度应该是完全不同的。假设您需要构建一条 SQL 语句来从数据库读取有关用户的信息:
>>>>>> def get_user_sql(user_id):
... return f"SELECT * FROM users WHERE user_id = '{user_id}'"
...
>>> user_id = "Bobby"
>>> get_user_sql(user_id)
"SELECT * FROM users WHERE user_id = 'Bobby'"
>>> user_id = input()
Robert'; DROP TABLE users; --
>>> get_user_sql(user_id)
"SELECT * FROM users WHERE user_id = 'Robert'; DROP TABLE users; --'"
这是一个改编版经典的SQL注入示例。恶意用户可以利用编写任意 SQL 代码的能力来造成严重破坏。如果执行了最后一条SQL语句,那么它将删除users
桌子。
有许多机制可以防御此类攻击。公众号 675在列表中再添加一个。一个新的类型被添加到typing
模块:LiteralString
是一种特殊的字符串类型,它是在代码中按字面定义的。
您可以使用LiteralString
标记容易受到用户控制字符串影响的函数。例如,执行SQL查询的函数可以注释如下:
from typing import LiteralString
def execute_sql(query: LiteralString):
# ...
类型检查器将特别注意传递的值的类型query
在这个函数中。以下字符串都将被允许作为参数execute_sql
:
>>>>>> execute_sql("SELECT * FROM users")
>>> table = "users"
>>> execute_sql("SELECT * FROM " + table)
>>> execute_sql(f"SELECT * FROM {table}")
最后两个例子没问题,因为query
由文字字符串构建。字符串仅被识别为LiteralString
如果字符串的所有部分都是按字面定义的。例如,以下示例将不会通过类型检查:
>>>>>> user_input = input()
users
>>> execute_sql("SELECT * FROM " + user_input)
尽管价值user_input
恰好与 的值相同table
从之前开始,类型检查器会在这里引发错误。用户控制的值user_input
并且可能会将其更改为对您的应用程序不安全的内容。如果您使用以下方法标记这些类型的易受攻击的函数LiteralString
、类型检查器将帮助您跟踪需要格外小心的情况。
可变参数泛型类型
A 泛型指定用其他类型参数化的类型,例如字符串列表或由整数、字符串和另一个整数组成的元组。 Python 使用方括号来参数化泛型。您将这两个示例写为list[str]
和tuple[int, str, int]
, 分别。
A 可变参数是一个接受可变数量参数的实体。例如,print()
是一个可变参数函数在Python中:
>>>>>> print("abc", 123, "def")
abc 123 def
您可以使用以下方法定义自己的可变参数函数*args 和 **kwargs捕获多个位置和关键字参数。
您可以使用typing.Generic
如果你想指定你自己的类是通用的。这是向量(也称为一维数组)的示例:
# vector.py
from typing import Generic, TypeVar
T = TypeVar("T")
class Vector(Generic[T]):
...
这类型变量 T
用作任何类型的替代。您可以使用Vector
在类型注释中如下:
>>>>>> from vector import Vector
>>> position: Vector[float]
在这个特定的例子中,T
将float
。为了使您的代码更清晰、类型更安全,您还可以使用类型别名甚至专用派生类型:
>>>>>> from typing import NewType
>>> from vector import Vector
>>> Coordinate = NewType("Coordinate", float)
>>> Coordinate(3.11)
3.11
>>> type(Coordinate(3.11))
<class 'float'>
>>> position: Vector[Coordinate]
这里,Coordinate
行为就像float
在运行时,但静态类型检查将区分Coordinate
和float
.
现在,假设您创建了一个更通用的数组类,可以处理可变数量的维度。到现在为止,已经有没有什么好办法指定这样的可变泛型.
公众号 646介绍打字.TypeVarTuple来处理这个用例。这些类型变量元组本质上是包装在元组中的任意数量的类型变量。您可以使用它们来定义具有任意维数的数组:
# ndarray.py
from typing import Generic, TypeVarTuple
Ts = TypeVarTuple("Ts")
class Array(Generic[*Ts]):
...
请注意解包运算符的使用(*
)。这是语法的必要部分,表明Ts
代表可变数量的类型。
笔记:您可以导入TypeVarTuple
从typing_extensions
适用于 3.11 之前的 Python 版本。但是,那*Ts
语法不适用于这些版本。作为等效的替代方案,您可以使用Typing_extensions.Unpack和写Unpack[Ts]
.
您可以使用NewType
标记数组中的维度或文字指定精确的形状:
>>>>>> from typing import Literal, NewType
>>> from ndarray import Array
>>> Height = NewType("Height", int)
>>> Width = NewType("Width", int)
>>> Channels = NewType("Channels", int)
>>> image: Array[Height, Width, Channels]
>>> video_frame: Array[Literal[1920], Literal[1080], Literal[3]]
你注释一下image
作为一个三维数组,其维度标记为Height
, Width
, 和Channels
。您不指定任何这些维度的大小。第二个例子,video_frame
,用文字值注释。在实践中,这意味着video_frame
必须是具体形状为1920×1080×3的数组。
这主要动机对于可变参数泛型来说,就像您在上面的示例中看到的那样输入数组。不过,也有其他用例. 数值模拟和其他数组库计划实施一旦工具就位,可变泛型就可以了。