Python 3.11 有什么新变化

  • 编者:
  • Pablo Galindo Salgado

这篇文章介绍了 Python 3.11 相比 3.10 增加的新特性。 Python 3.11 发布于 2022 年 10 月 24 日。 要了解更详细的信息,可参阅 更新日志

摘要 — 发布重点

  • Python 3.11 的速度比 Python 3.10 快 10-60%。在平均状况下,在标准基准测试(standard benchmark suite)中可见1.25倍的加速效果。更多细节请参见 更快的 CPython 一节。

新的语法特性:

新的内置特性:

新的标准库模块:

解释器的改进:

新的类型标注特性:

重要的弃用、移除或限制:

新的特性

PEP 657:回溯信息中标注更详细的错误位置

在打印回溯信息(traceback)时,解释器现在不仅会指出错误所在行,还会进一步指出引发错误的表达式在哪里。例如:

  1. Traceback (most recent call last):
  2. File "distance.py", line 11, in <module>
  3. print(manhattan_distance(p1, p2))
  4. ^^^^^^^^^^^^^^^^^^^^^^^^^^
  5. File "distance.py", line 6, in manhattan_distance
  6. return abs(point_1.x - point_2.x) + abs(point_1.y - point_2.y)
  7. ^^^^^^^^^
  8. AttributeError: 'NoneType' object has no attribute 'x'

先前版本的解释器只会指出该行存在错误,我们不清楚哪个对象是这里的 None。在深度嵌套的 dict 对象以及连用多个函数调用的场景下,这些增强的错误也会大有裨益:

  1. Traceback (most recent call last):
  2. File "query.py", line 37, in <module>
  3. magic_arithmetic('foo')
  4. File "query.py", line 18, in magic_arithmetic
  5. return add_counts(x) / 25
  6. ^^^^^^^^^^^^^
  7. File "query.py", line 24, in add_counts
  8. return 25 + query_user(user1) + query_user(user2)
  9. ^^^^^^^^^^^^^^^^^
  10. File "query.py", line 32, in query_user
  11. return 1 + query_count(db, response['a']['b']['c']['user'], retry=True)
  12. ~~~~~~~~~~~~~~~~~~^^^^^
  13. TypeError: 'NoneType' object is not subscriptable

在复杂的算数表达式中同样有用:

  1. Traceback (most recent call last):
  2. File "calculation.py", line 54, in <module>
  3. result = (x / y / z) * (a / b / c)
  4. ~~~~~~^~~
  5. ZeroDivisionError: division by zero

此外,增强的回溯信息功能使用的信息通过通用 API 提供,该 API 可用于将 bytecode 指令 与源代码位置相关联。 可以使用以下方式获取此信息:

更多细节请参见 PEP 657 [https://peps.python.org/pep-0657/]。(由Pablo Galindo、Batuhan Taskaya 和 Ammar Askar 在 bpo-43950 [https://bugs.python.org/issue?@action=redirect&bpo=43950] 中贡献)

备注

该特性需要在 代码对象 中存储列位置,这可能会导致解释器内存占用和经过编译的 Python 文件的文件大小略有增加。 要避免存储额外的信息同时取消打印额外的回溯信息,请使用 -X no_debug_ranges 命令行选项或 PYTHONNODEBUGRANGES 环境变量。

PEP 654:异常组与 except*

PEP 654 [https://peps.python.org/pep-0654/] 引入了若干语言特性,从而让程序能够同时引发和处理多个不相关的异常。内置类型 ExceptionGroupBaseExceptionGroup 使得将异常划分成组并一起引发成为可能,新添加的 except* 是对 except 的泛化语法,这一语法能够匹配异常组的子组。

更多细节请参见 PEP 654 [https://peps.python.org/pep-0654/]。

(由 Irit Katriel 在 bpo-45292 [https://bugs.python.org/issue?@action=redirect&bpo=45292] 中贡献,PEP 由 Irit Katriel、Yury Selivanov 和 Guido van Rossum 编写)

PEP 678:可用注释丰富异常

add_note() 方法已被添加到 BaseException 中。如果存在引发异常时不可用的上下文信息,使用该方法可以手动附加这些信息来丰富异常。添加的备注会显示在默认的回溯信息中。

更多细节请参见 PEP 678 [https://peps.python.org/pep-0678/]。

(由 Irit Katriel 在 bpo-45607 [https://bugs.python.org/issue?@action=redirect&bpo=45607] 中贡献,PEP 由 Zac Hatfield-Dodds 编写)

Windows 下的 py.exe 启动器改进

包括在 Python 3.11 中的 适用于Windows的Python启动器 的副本已进行了重大更新。 现在它支持 PEP 514 [https://peps.python.org/pep-0514/] 所定义的 company/tag 语法即使用 -V:/ 参数代替受限的 -.。 这允许启动托管在 python.org [https://www.python.org] 上的 PythonCore 以外的其他发行版。

当使用 -V: 选择器时,可以省略 company 或 tag,此时会搜索所有的安装。例如,-V:OtherPython/ 会选择 OtherPython 所注册的“最佳”标签,而 -V:3.11-V:/3.11 则会选择标签为 3.11 的“最佳”发行版。

在使用旧式的 --.---.- 参数时,应保留过去版本的所有已有行为,并只选择从 PythonCore 发布的版本。 不过,-64 后缀现在表示“非 32 位”(不一定是 x86-64),因为有多种受支持的 64 位平台。 32 位运行时是通过检查运行时的标签是否有 -32 后缀来检测的。 自 Python 3.5 以来的所有版本都在其 32 位编译中包括了这个后缀。

有关类型提示的新增特性

本节介绍了涉及 PEP 484 [https://peps.python.org/pep-0484/] 类型提示和 typing 模块的主要更改。

PEP 646:可变参数泛型

之前的 PEP 484 [https://peps.python.org/pep-0484/] 引入了 TypeVar,其支持创建带单一类型参数的泛型。 PEP 646 [https://peps.python.org/pep-0646/] 新引入了 TypeVarTuple,其支持 任意 数量的类型的参数化。换言之,TypeVarTuple可变参数(variadic) 类型变量,支持 可变参数 泛型。

该泛型的引入让相当多的代码写法成为可能。特别是在诸如 NumPy 和 TensorFlow 这样的数值计算库中,这种泛型让类数组(array-like)结构类型可以用数组的 形状(shape) 来参数化。这样一来,静态类型检查器就能够在使用这些库的代码中捕获与形状有关的错误了。

更多细节请参见 PEP 646 [https://peps.python.org/pep-0646/]。

(由 Matthew Rahtz 在 bpo-43224 [https://bugs.python.org/issue?@action=redirect&bpo=43224] 中贡献,共同贡献的还有 Serhiy Storchaka 和 Jelle Zijlstra,PEP 由 Mark Mendoza、Matthew Rahtz、Pradeep Kumar Srinivasan 以及 Vincent Siles 编写)

PEP 655:将单个 TypedDict 项标记为必填或非必填项

RequiredNotRequired 提供了一种简单明了的方式来标记 TypedDict 中的单个项是否必须存在。而在之前的版本中,这只能通过使用继承来实现。

默认情况下,所有字段仍然是必填的,除非 total 参数设置为 False,在这种情况下,默认情况下所有字段则是非必填的。例如,下面指定了一个 TypedDict,其中有一个必填的键和一个非必填的键:

  1. class Movie(TypedDict):
  2. title: str
  3. year: NotRequired[int]
  4.  
  5. m1: Movie = {"title": "Black Panther", "year": 2018} # 可以
  6. m2: Movie = {"title": "Star Wars"} # 可以 (不是必需的)
  7. m3: Movie = {"year": 2022} # 错误 (缺少必需的字段 title)

而以下的定义和上述定义等价:

  1. class Movie(TypedDict, total=False):
  2. title: Required[str]
  3. year: int

更多细节请参见 PEP 655 [https://peps.python.org/pep-0655/]。

(由 David Foster 和 Jelle Zijlstra 在 bpo-47087 [https://bugs.python.org/issue?@action=redirect&bpo=47087] 中贡献,PEP 由 David Foster 编写)

PEP 673:Self 类型

新的 Self 注解提供了一种简单而又直观的方法来标注返回其类实例的方法。这一注解的行为与 PEP 484 [https://peps.python.org/pep-0484/#annotating-instance-and-class-methods] 中指定的基于 TypeVar 的方法是一致的,但更简洁、更易于遵循。

常见的用法包括以 classmethod() 形式提供的替代构造函数,以及返回 self__enter__() 方法:

  1. class MyLock:
  2. def __enter__(self) -> Self:
  3. self.lock()
  4. return self
  5.  
  6. ...
  7.  
  8. class MyInt:
  9. @classmethod
  10. def fromhex(cls, s: str) -> Self:
  11. return cls(int(s, 16))
  12.  
  13. ...

Self 也可以用来标注与其封闭类类型相同的方法参数或属性。

更多细节请参见 PEP 673 [https://peps.python.org/pep-0673/]。

(由 James Hilton-Balfe 在 bpo-46534 [https://bugs.python.org/issue?@action=redirect&bpo=46534] 中贡献,PEP 由 Pradeep Kumar Srinivasan 和 James Hilton-Balfe 编写)

PEP 675:任意字面值字符串类型

新的 LiteralString 注解能用于注明函数参数可为任何字面值字符串类型。这允许函数接受任意字面值字符串类型,以及从其他字面值字符串创建的字符串。这样一来,类型检查器就可以强制对此敏感的函数(例如执行 SQL 语句或 shell 命令的函数)只以静态的实参来调用,从而提供对注入攻击的保护。

例如,SQL 查询函数可按照如下方式注解:

  1. def run_query(sql: LiteralString) -> ...
  2. ...
  3.  
  4. def caller(
  5. arbitrary_string: str,
  6. query_string: LiteralString,
  7. table_name: LiteralString,
  8. ) -> None:
  9. run_query("SELECT * FROM students") # 可以
  10. run_query(query_string) # 可以
  11. run_query("SELECT * FROM " + table_name) # 可以
  12. run_query(arbitrary_string) # 类型检查器错误
  13. run_query( # 类型检查器错误
  14. f"SELECT * FROM students WHERE name = {arbitrary_string}"
  15. )

请参阅 PEP 675 [https://peps.python.org/pep-0675/] 了解详情。

(由 Jelle Zijlstra 在 bpo-47088 [https://bugs.python.org/issue?@action=redirect&bpo=47088] 中贡献,PEP 由 Pradeep Kumar Srinivasan 和 Graham Bleaney 编写)

PEP 681:数据类变换

dataclass_transform 可用于修饰类、元类或本身是装饰器的函数。使用 @dataclass_Transform() 就能让静态类型检查器知道被修饰的对象会在运行时执行对类的变换专业的“魔法”,从而让它具有类似 dataclass 的行为。

例如:

  1. # create_model 装饰器是由库来定义的。
  2. @typing.dataclass_transform()
  3. def create_model(cls: Type[T]) -> Type[T]:
  4. cls.__init__ = ...
  5. cls.__eq__ = ...
  6. cls.__ne__ = ...
  7. return cls
  8.  
  9. # 现在 create_model 装饰器可被用于创建新的模型类:
  10. @create_model
  11. class CustomerModel:
  12. id: int
  13. name: str
  14.  
  15. c = CustomerModel(id=327, name="Eric Idle")

更多细节请参见 PEP 681 [https://peps.python.org/pep-0681/]。

(由 Jelle Zijlstra 在 gh-91860 [https://github.com/python/cpython/issues/91860] 中贡献,PEP 由 Erik De Bonte 和 Eric Traut 编写)

未来版本可能不再实现 PEP 563

原计划随 Python 3.10 发布的 PEP 563 [https://peps.python.org/pep-0563/] 延迟注解求值(from __future__ import annotationsfuture 语句)已被无限期搁置。 更多信息请参见 指导委员会(Steering Council)邮件列表中的讨论 [https://mail.python.org/archives/list/python-dev@python.org/message/VIZEBX5EYMSYIJNDBF6DMUMZOCWHARSO/] 。

其他语言特性修改

其他 CPython 实现的改变

新增模块

改进的模块

asyncio

contextlib

dataclasses

datetime

enum

  • EnumMeta 重命名为 EnumType (EnumMeta 作为别名保留)。

  • 增加了 StrEnum,其成员可以(且必须)作为字符串使用。

  • 增加了 ReprEnum,它只是在为 __str__()__format__() 方法(供 str(), format()f-string 使用)返回成员的字面值(而不是名称)时修改了它们的 __repr__()

  • 修改了 Enum.__format__() (为 format(), str.format()f-string 的默认值) 以便始终产生于 Enum.__str__() 相同的结果:对于继承自 ReprEnum 的枚举它将成为其成员的值;对于所有其他枚举它将为枚举和成员名称 (例如 Color.RED)。

  • 将新的 boundary 类形参连同其选项添加到 Flag 枚举和 FlagBoundary 枚举中,以控制超范围旗标值的处理方式。

  • 增加了 verify() 枚举装饰器和 EnumCheck 枚举及其选项,以基于特定约束条件来检查枚举类。

  • 增加了 member()nonmember() 装饰器,用于确保被装饰的对象是/否会被转换为枚举成员。

  • 增加了 property() 装饰器,它类似于 property() 但是专门针对枚举。 请使用它来代替 types.DynamicClassAttribute()

  • 增加了 global_enum() 枚举装饰器,它会调整 __repr__()__str__() 以将值显示为其模块的成员而不是枚举类的成员。 例如,'re.ASCII're.RegexFlagASCII 成员而不是 'RegexFlag.ASCII'

  • 增强了 Flag 以支持针对其成员的 len(),迭代和 in/not in。 例如,现在可以使用下面的代码: len(AFlag(3)) == 2 and list(AFlag(3)) == (AFlag.ONE, AFlag.TWO)

  • 修改了 EnumFlag 使得成员的定义是在 __init_subclass__() 被调用之前;dir() 现在将包括来自混入数据类型的方法等。

  • Flag 修改为只考虑规范的基本值(即二的乘方)而复合值(如 3, 6, 10 等)则被视为别名;逆向旗标将被强制转换为对应的正向旗标。

fcntl

  • 在 FreeBSD 上,F_DUP2FDF_DUP2FD_CLOEXEC 旗标分别受到支持,前者等价于 dup2 用法而后者额外设置了 FD_CLOEXEC 旗标。

fractions

functools

  1. >>> from functools import singledispatch
  2. >>> @singledispatch
  3. ... def fun(arg, verbose=False):
  4. ... if verbose:
  5. ... print("Let me just say,", end=" ")
  6. ... print(arg)
  7. ...
  8. >>> @fun.register
  9. ... def _(arg: int | float, verbose=False):
  10. ... if verbose:
  11. ... print("Strength in numbers, eh?", end=" ")
  12. ... print(arg)
  13. ...
  14. >>> from typing import Union
  15. >>> @fun.register
  16. ... def _(arg: Union[list, set], verbose=False):
  17. ... if verbose:
  18. ... print("Enumerate this:")
  19. ... for i, elem in enumerate(arg):
  20. ... print(i, elem)
  21. ...

(由 Yurii Karabas 在 bpo-46014 [https://bugs.python.org/issue?@action=redirect&bpo=46014] 中贡献。)

gzip

  • 现在 gzip.compress() 函数当传入 mtime=0 参数时会更快速因为它把压缩任务完全委托给单独的 zlib.compress() 操作。 此项改变有一个附带影响:gzip 文件标头将在其标头中包含一个 "OS" 字节。 在传统上它总是会被 gzip 模块设为代表 "unknown" 的值 255。 现在,当使用 compress() 并传入 mtime=0 时,它可以被 Python 所链接的下层 zlib C 库设为不同的值。 (请参阅 gh-112346 [https://github.com/python/cpython/issues/112346] 了解此附带影响的详情。)

hashlib

IDLE 与 idlelib

inspect

(由 Pablo Galindo 在 gh-88116 [https://github.com/python/cpython/issues/88116] 中贡献。)

locale

logging

math

operator

os

pathlib

re

shutil

socket

sqlite3

string

sys

sysconfig

  • 增加了三个新的 安装方案 (posix_venv, nt_venv and venv) 并将在 Python 创建新虚拟环境或从虚拟环境运行时使用。 前两个方案 (posix_venv 和 nt_venv) 是用于非 Windows 和 Windows 的 OS 专属方案,venv 实际上是根据 Python 运行所在的 OS 来确定的前两者之一。 这对于要修改 sysconfig.get_preferred_scheme() 的下游分发者来说很有用处。 创建新虚拟环境的第三方代码应当使用新的 venv 安装方案来确定路径,就像 venv 所做的那样。 (由 Miro Hrončok 在 bpo-45413 [https://bugs.python.org/issue?@action=redirect&bpo=45413] 中贡献。)

tempfile

threading