geekdoc-python-zh/docs/realpython/python311-tomllib.md

44 KiB
Raw Permalink Blame History

Python 3.11 预览版:TOML 和 tomllib

原文:https://realpython.com/python311-tomllib/

Python 3.11 离最终发布越来越近,最终发布将在 2022 年 10 月。新版本目前正在进行 beta 测试,你可以自己安装它来预览和测试一些新功能,包括支持使用新的tomllib模块读取 TOML。

TOML 是一种配置文件格式,在 Python 生态系统中越来越流行。这是由采用pyproject.toml作为 Python 打包中的中央配置文件所驱动的。其他重要的工具,像黑色mypypytest 也使用 TOML 进行配置。

在本教程中,您将:

  • 在你的电脑上安装 Python 3.11 测试版,就在你当前安装的 Python 旁边
  • 熟悉 TOML 格式的基础知识
  • 使用新的tomllib模块读取 TOML 文件
  • 用第三方库编写 TOML ,了解为什么这个功能没有包含在tomllib
  • 探索 Python 3.11 新的类型特性,包括SelfLiteralString类型以及可变泛型

Python 3.11 中还有许多其他新特性和改进。查看变更日志中的新增内容以获得最新列表,并在 Real Python 上阅读其他 Python 3.11 预览版以了解其他特性。

免费奖励: 掌握 Python 的 5 个想法,这是一个面向 Python 开发者的免费课程,向您展示将 Python 技能提升到下一个水平所需的路线图和心态。

Python 3.11 测试版

Python 的新版本在每年 10 月发布。代码是在发布日期前经过 17 个月的时间开发和测试的。新功能在 alpha 阶段实现。对于 Python 3.11,在 2021 年 10 月至 2022 年 4 月期间,共发布了七个 alpha 版本

Python 3.11 的第一个 beta 版本发生在 2022 年 5 月 8 日凌晨。每个这样的预发布都由一个发布经理协调——目前是Pablo ga lindo Salgado——并将来自 Python 核心开发者和其他志愿者的数百个提交集合在一起。

这个版本也标志着新版本的功能冻结。换句话说Python 3.11.0b1 中没有的新功能不会添加到 Python 3.11 中。相反,功能冻结和发布日期(2022 年 10 月 3 日)之间的时间用于测试和巩固代码。

大约每月一次测试阶段Python 的核心开发者发布一个新的测试版本,继续展示新特性,测试它们,并获得早期反馈。目前 Python 3.11 的最新测试版是 3.11.0b3 ,发布于2022 年6 月 1 日。

**注:**本教程使用的是 Python 3.11 的第三个 beta 版本。如果您使用更高版本,可能会遇到一些小的差异。然而,tomllib建立在一个成熟的库之上,你可以预期你在本教程中学到的东西将在 Python 3.11 的测试阶段和最终版本中保持不变。

如果你在维护你自己的 Python 包,那么测试阶段是一个重要的时期,你应该开始用新版本测试你的包。核心开发人员与社区一起,希望在最终发布之前找到并修复尽可能多的 bug。

Remove ads

很酷的新功能

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 版本。在这一小节中,你将学习几种不同的方法:使用 Docker ,使用 pyenv ,或者从安装。选择最适合您和您的系统的一个。

**注意:**测试版是即将推出的功能的预览。虽然大多数特性都可以很好地工作,但是你不应该依赖任何 Python 3.11 beta 版本的产品,或者任何潜在错误会带来严重后果的地方。

如果您可以在您的系统上访问 Docker ,那么您可以通过拉取并运行python:3.11-rc-slim Docker 镜像来下载最新版本的 Python 3.11:

$ 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-update 插件更新您的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 插件来设置虚拟环境并激活它。

你也可以从python.org的预发布版本中安装 Python。选择最新预发布,向下滚动到页面底部的文件部分。下载并安装与您的系统对应的文件。更多信息参见 Python 3 安装&设置指南

本教程中的大多数示例都依赖于新特性,因此您应该使用 Python 3.11 可执行文件来运行它们。具体如何运行可执行文件取决于您的安装方式。如果你需要帮助,那么看看关于 Dockerpyenv虚拟环境或者从源码安装的相关教程。

Remove ads

tomllibPython 3.11 中的 TOML 解析器

Python 是一门成熟的语言。Python 的第一个公共版本发布于 30 多年前的 1991 年。Python 的许多独特特性,包括显式异常处理、对空白的依赖以及丰富的数据结构,如列表和字典,甚至在早期的就已经存在。

然而Python 的第一个版本缺少的一个特性是共享社区包和模块的便捷方式。这并不奇怪。事实上Python 和万维网几乎是同时发明的。1991 年底,全世界只有12 台网络服务器,而且没有一台是专门用于发布 Python 代码的。

随着时间的推移Python 和 T2 互联网变得越来越流行。几个倡议旨在允许共享 Python 代码。这些特性有机地发展,导致 Python 与打包的关系有些混乱。

在过去的几十年里,这个问题已经通过几个打包 pep(Python 增强提案)得到了解决,对于库维护者最终用户来说,情况已经有了很大的改善。

一个挑战是构建包依赖于执行一个setup.py文件,但是没有机制知道该文件依赖于哪些依赖项。这就产生了一种先有鸡还是先有蛋的问题,你需要运行setup.py来发现如何运行setup.py

实际上,pip——Python 的包管理器——假设它应该使用 Setuptools 来构建包,并且 Setuptools 在你的计算机上是可用的。这使得使用像 Flitpoems这样的替代构建系统变得更加困难。

为了解决这种情况, PEP 518 引入了 pyproject.toml 配置文件,它指定了 Python 项目构建依赖关系。2016 年接受了 PEP 518。当时TOML 仍然是一种相当新的格式,而且 Python 或其标准库中没有对解析 TOML 的内置支持。

随着 TOML 格式的成熟和pyproject.toml文件的使用Python 3.11 增加了对解析 TOML 文件的支持。在这一节中,您将了解更多关于什么是 TOML 格式,如何使用新的tomllib来解析 TOML 文档,以及为什么tomllib不支持编写 TOML 文件。

学习基本的 TOML

Tom Preston-Werner 先是宣布 Tom 的显而易见、极简的语言——俗称TOML——并于 2013 年发布了其规范的版本 0.1.0 。从一开始TOML 的目标就是提供一种“最小化的配置文件格式,由于语义明显,易于阅读”( Source )。TOML 规范的稳定版本 1.0.0 于 2021 年 1 月发布。

TOML 文件是一个 UTF-8 编码的区分大小写的文本文件。TOML 中的主要构件是键-值对,其中键与值用等号(=)隔开:

version  =  3.11

在这个最小的 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 list。一个是一个键值对的嵌套集合,类似于 Python dict

使用方括号将数组的元素括起来。表格从命名表格的[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),
    },
}

TOML 中的[python]键在 Python 中由字典中的"python"键表示,指向包含 TOML 部分中所有键值对的嵌套字典。TOML 表可以任意嵌套,一个 TOML 文档可以包含几个 TOML 表。

这就结束了对 TOML 语法的简短介绍。虽然 TOML 的设计有一个相当简单的语法,但是这里还有一些细节没有涉及到。要深入了解,请查看 Python 和 TOML:新的最好的朋友TOML 规范

除了语法之外,您还应该考虑如何解释 TOML 文件中的值。TOML 文档通常用于配置。最终,其他一些应用程序会使用 TOML 文档中的信息。因此,该应用程序对 TOML 文件的内容有一些期望。这意味着一个 TOML 文档可能有两种不同的错误:

  1. **语法错误:**TOML 文档不是有效的 TOML。TOML 解析器通常会捕捉到这一点。
  2. **模式错误:**TOML 文档是有效的 TOML但是它的结构不是应用程序所期望的。应用程序本身必须处理这个问题。

TOML 规范目前还不包括一种可以用来验证 TOML 文档结构的模式语言,尽管有几个提案存在。这种模式将检查给定的 TOML 文档是否包含给定用例的正确的表、键和值类型。

作为一个非正式模式的例子, PEP 517PEP 518 说一个pyproject.toml文件应该定义build-system表,该表必须包括关键字requiresbuild-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 构建自己的包的知识,请查看如何向 PyPI 发布开源 Python 包

您可以自己检查这个验证。创建以下错误的pyproject.toml文件:

# pyproject.toml [build-system] requires  =  "setuptools>=61.0.0" backend  =  "setuptools.build_meta"

这是有效的 TOML因此该文件可以被任何 TOML 解析器读取。但是,根据 PEPs 中的要求,它不是有效的build-system表。为了确认这一点,安装 build ,这是一个符合 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 文件。

Remove ads

tomllib读 TOML】

Python 3.11 在标准库中新增了一个模块,名为 tomllib 。您可以使用tomllib来读取和解析任何符合 TOML v1.0 的文档。在这一小节中,您将学习如何直接从文件和包含 TOML 文档的字符串中加载 TOML。

PEP 680 描述了tomllib和一些导致 TOML 支持被添加到标准库中的过程。在 Python 3.11 中包含tomllib的两个决定性因素是pyproject.toml在 Python 打包生态系统中扮演的核心角色,以及 TOML 规范将在 2021 年初达到 1.0 版本。

tomllib的实现或多或少是直接从 tomli 中剽窃来的,他也是 PEP 680 的合著者之一。

tomllib模块非常简单,因为它只包含两个函数:

  1. load() 从文件中读取 TOML 文件。
  2. loads() 从字符串中读取 TOML 文件。

您将首先看到如何使用tomllib来读取下面的pyproject.toml文件,它是 tomli 项目中相同文件的简化版本:

# 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 类型:strfloatintbool,以及字典列表datetime对象tomllib文档包括一个转换表,它展示了如何用 Python 表示 TOML 类型。

如果您愿意,那么您可以使用loads()结合pathlib从文件中读取 TOML:

>>> 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

在这个例子中,你使用load()loads()来加载pyproject.toml。然后确认无论如何加载文件Python 表示都是相同的。

load()loads()都接受一个可选参数: parse_float 。这允许您控制如何用 Python 解析和表示浮点数。默认情况下,它们被解析并存储为float对象,在大多数 Python 实现中,这些对象是 64 位的,精度为精度的大约 16 位十进制数字。

另一种方法是,如果你需要用更精确的数字,用 decimal.Decimal 代替:

>>> 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模块。如果你想在需要支持旧版本 Python 的代码库上使用 TOML 和tomllib,那么你可以依靠tomli。为此,请在您的需求文件中添加以下行:

tomli >= 1.1.0 ; python_version < "3.11"

这将在 3.11 之前的 Python 版本上使用时安装tomli。在您的源代码中,您可以适当地使用tomllibtomli和下面的导入:

try:
    import tomllib
except ModuleNotFoundError:
    import tomli as tomllib

这段代码将在 Python 3.11 和更高版本中导入tomllib。如果tomllib不可用,那么tomli被导入并别名为tomllib名。

您已经看到了如何使用tomllib来读取 TOML 文档。您可能想知道如何编写 TOML 文件。原来不能用tomllib写 TOML。请继续阅读了解原因并查看一些替代方案。

Remove ads

写入 tomlt0

类似的现有库如jsonpickle包括load()dump()函数,后者用于写数据。dump()功能,以及相应的dumps(),被故意排除在tomllib之外。

根据 PEP 680 和围绕它的讨论,这样做有几个原因:

  • tomllib包含在标准库中的主要动机是为了能够读取生态系统中使用的 TOML 文件。

  • TOML 格式被设计成一种对人友好的配置格式,所以许多 TOML 文件都是手工编写的。

  • TOML 格式不是像 JSON 或 pickle 那样的数据序列化格式,所以没有必要完全与jsonpickleAPI 保持一致。

  • TOML 文档可能包含在写入文件时应该保留的注释和格式。这与将 TOML 表示为基本 Python 类型不兼容。

  • 关于如何布局和格式化 TOML 文件有不同的观点。

  • 没有一个核心开发人员表示有兴趣为tomllib维护一个写 API。

一旦某些东西被添加到标准库中,就很难更改或删除,因为有人依赖它。这是一件好事,因为这意味着 Python 在很大程度上保持了向后兼容:在 Python 3.10 上运行的 Python 程序很少会在 Python 3.11 上停止工作。

另一个后果是,核心团队对添加新功能持保守态度。如果有明确的需求,可以在以后添加对编写 TOML 文档的支持。

不过,这不会让你空手而归。有几个第三方 TOML 编写器可用。tomllib文档提到了两个包:

  • tomli-w 顾名思义就是可以写 TOML 文档的tomli的兄弟姐妹。这是一个简单的模块,没有很多选项来控制输出。
  • tomlkit 是一个强大的处理 TOML 文档的软件包它支持读写。它保留注释、缩进和其他空白。TOML 工具包是为开发和使用的。

根据您的用例,其中一个包可能会满足您的 TOML 编写需求。

如果你不想仅仅为了写一个 TOML 文件而添加一个外部依赖,那么你也可以试着滚动你自己的 writer。以下示例显示了一个不完整的 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()中。它使用结构模式匹配基于value的类型构造不同种类的 TOML 字符串。

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"

这里你先用tomllibpyproject.toml。然后使用自己的tomllib_w模块将 TOML 文档写回控制台。

如果你需要更好的支持来编写 TOML 文档,你可以扩展一下tomllib_w。然而,在大多数情况下,你应该依赖一个现有的包,比如tomli_w或者tomlkit

虽然 Python 3.11 不支持编写 TOML 文件,但是包含的 TOML 解析器对许多项目都很有用。接下来,您可以将 TOML 用于您的配置文件,因为您知道在 Python 中读取它们将获得一流的支持。

Remove ads

其他新功能

TOML 支持当然值得庆祝,但是 Python 3.11 中也有一些小的改进。在很长一段时间里Python 的类型检查领域已经出现了这样的增量变化。

PEP 484 引入了类型提示。它们从 Pyhon 3.5 开始就可用了,每一个新的 Python 版本都为静态类型系统增加了功能 PyCon US 2022 大会的主题演讲中,茹卡兹·兰加谈到了类型检查。

Python 3.11 接受了几个新的与类型相关的 pep。您将很快了解到更多关于Self类型、LiteralString类型和可变泛型的知识。

**注意:**类型检查增强有点特殊,因为它们依赖于您的 Python 版本和类型检查工具的版本。最新的测试版支持一些新的 Python 3.11 类型系统特性,但是还没有在所有的类型检查器中实现。

例如,你可以在他们的 GitHub 页面上监控 mypy 的对新功能的支持状态。

甚至还有一些新的与打字相关的特性,下面就不介绍了。 PEP 681 增加了@dataclass_transform 装饰器,可以标记语义类似于数据类的类。此外, PEP 655 允许您在类型化词典中标记必填和可选字段。

自身类型

PEP 673 引入了一个新的Self类型,它动态地引用当前的类。当您用返回类实例的方法实现类时,这很有用。考虑由极坐标表示的二维点的以下部分实现:

# 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是特意选择来模仿公式中使用的数学符号。

一般来说,建议为属性使用更长更具描述性的名称。然而,有时候遵循你的问题领域的惯例也是有用的。如果你愿意的话,可以随意用.radius替换.r,用.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

这里,首先创建一个表示笛卡尔点(34)的点。在极坐标中,这个点用半径r = 5.0,角度φ ≈ 0.927 来表示。您可以使用公式x = r * cos(φ)转换回笛卡尔x坐标。

现在,您想给.from_xy()添加类型提示。它返回一个PolarPoint对象。然而,在这一点上你不能直接使用PolarPoint作为注释,因为那个类还没有被完全定义。相反,您可以使用带引号的"PolarPoint"或者添加一个 PEP 563 future import使推迟注释的求值

这两种变通方法都有其缺点,目前推荐的是用一个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类型,就可以更方便地使用类和面向对象的特性(如继承)向项目添加静态类型。

Remove ads

任意文字字符串类型

Python 3.11 中的另一个新类型是LiteralString。虽然这个名字可能会让你想起 Python 3.8 中添加的Literal,但是LiteralString的主要用例有点不同。要理解将它添加到类型系统的动机,首先退一步考虑字符串。

一般来说Python 不关心如何构造字符串:

>>> s1 = "Python"
>>> s2 = "".join(["P", "y", "t", "h", "o", "n"])
>>> s3 = input()
Python 
>>> s1 == s2 == s3
True

在这个例子中,您以三种不同的方式创建字符串"Python"。首先,将它指定为一个文字字符串。接下来,将六个单字符字符串连接起来,形成字符串"Python"。最后,您使用 input() 从用户输入中读取字符串。

最后的测试显示每个字符串的值是相同的。在大多数应用程序中,您不需要关心特定的字符串是如何构造的。但是,有些时候您需要小心,尤其是在处理用户输入时。

不幸的是,针对数据库的攻击非常普遍。 Java Log4j 漏洞同样利用日志系统执行任意代码。

回到上面的例子。虽然s1s3的值恰好相同,但是您对这两个字符串的信任应该是完全不同的。假设您需要构建一个 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表。

有许多机制可以抵御这类攻击。 PEP 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标记这些易受攻击的函数,类型检查器将帮助您跟踪需要格外小心的情况。

可变泛型类型

一个通用类型指定了一个用其他类型参数化的类型例如一个字符串列表或一个由一个整数、一个字符串和另一个整数组成的元组。Python 使用方括号来参数化泛型。你把这两个例子分别写成list[str]tuple[int, str, int]

一个变量是一个接受可变数量参数的实体。例如,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,但是静态类型检查将区分Coordinatefloat

现在,假设您创建了一个更通用的数组类,它可以处理可变数量的维度。直到现在,还没有好的方法来指定这样的可变泛型

PEP 646 引入 typing.TypeVarTuple 来处理这个用例。这些类型变量元组本质上是包装在元组中的任意数量的类型变量。您可以使用它们来定义任意维数的数组:

# ndarray.py

from typing import Generic, TypeVarTuple

Ts = TypeVarTuple("Ts")

class Array(Generic[*Ts]):
    ...

注意解包操作符(*)的使用。这是语法的必要部分,表明Ts代表可变数量的类型。

**注意:**在 3.11 之前的 Python 版本上可以从typing_extensions导入TypeVarTuple。然而,*Ts语法在这些版本上不起作用。作为等价的替代,你可以用 typing_extensions.Unpack 并写成Unpack[Ts]

您可以使用NewType来标记数组中的尺寸,或者使用 Literal 来指定精确的形状:

>>> 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标注为一个三维数组,其维度标记为HeightWidthChannels。您不需要指定这些维度的大小。第二个例子,video_frame,用文字值注释。实际上,这意味着video_frame必须是一个特定形状为 1920 × 1080 × 3 的数组。

可变泛型的主要动机是对数组进行类型化,就像你在上面的例子中看到的那样。然而,也有其他用例。一旦工具到位, NumPy其他数组库计划实现可变泛型。

Remove ads

结论

在本教程中,您了解了 Python 3.11 中的一些新特性。虽然最终版本将于 2022 年 10 月发布,但你已经可以下载测试版并尝试新功能。在这里,您已经探索了新的tomllib模块,并逐渐熟悉了 TOML 格式。

您已经完成了以下操作:

  • 在您的计算机上安装了 Python 3.11 测试版,就在您当前安装的 Python 旁边
  • 使用新的tomllib模块读取 TOML 文件
  • 用第三方库编写了 TOML 并创建了自己的函数来编写 TOML 的子集
  • 探索 Python 3.11 新的类型特性,包括SelfLiteralString类型以及可变泛型

你已经在你的项目中使用 TOML 了吗?尝试新的 TOML 解析器,并在下面的评论中分享你的经验。

免费奖励: 掌握 Python 的 5 个想法,这是一个面向 Python 开发者的免费课程,向您展示将 Python 技能提升到下一个水平所需的路线图和心态。*************