Python 3.11 有什么新变化

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 654:异常组与 except*

新的内置特性:

  • PEP 678:可用注释丰富异常

新的标准库模块:

解释器的改进:

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

  • 新增 -P 命令行选项以及 PYTHONSAFEPATH 环境变量来 禁止自动将潜在的不安全路径前置 到 sys.path

新的类型标注特性:

  • PEP 646:可变参数泛型

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

  • PEP 673:Self 类型

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

  • PEP 681:数据类变换

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

  • PEP 594: 许多旧标准库模块已被弃用,并将在 Python 3.13 中移除

  • PEP 624: Py_UNICODE 编码器 API 已被移除

  • PEP 670: 转换为静态内联函数的宏

新的特性

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

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

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

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

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

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

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

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

更多细节请参见 PEP 657。(由Pablo Galindo、Batuhan Taskaya 和 Ammar Askar 在 bpo-43950 中贡献)

备注

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

PEP 654:异常组与 except*

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

更多细节请参见 PEP 654。

(由 Irit Katriel 在 bpo-45292 中贡献,PEP 由 Irit Katriel、Yury Selivanov 和 Guido van Rossum 编写)

PEP 678:可用注释丰富异常

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

更多细节请参见 PEP 678。

(由 Irit Katriel 在 bpo-45607 中贡献,PEP 由 Zac Hatfield-Dodds 编写)

Windows 下的 py.exe 启动器改进

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

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

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

有关类型提示的新增特性

本节介绍了涉及 PEP 484 类型提示和 typing 模块的主要更改。

PEP 646:可变参数泛型

之前的 PEP 484 引入了 TypeVar,其支持创建带单一类型参数的泛型。PEP 646 新引入了 TypeVarTuple,其支持 任意 数量的类型的参数化。换言之,TypeVarTuple 是 可变参数(variadic) 类型变量,支持 可变参数 泛型。

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

更多细节请参见 PEP 646。

(由 Matthew Rahtz 在 bpo-43224 中贡献,共同贡献的还有 Serhiy Storchaka 和 Jelle Zijlstra,PEP 由 Mark Mendoza、Matthew Rahtz、Pradeep Kumar Srinivasan 以及 Vincent Siles 编写)

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

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

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

class Movie(TypedDict):
   title: str
   year: NotRequired[int]

m1: Movie = {"title": "Black Panther", "year": 2018}  # OK
m2: Movie = {"title": "Star Wars"}  # OK (year is not required)
m3: Movie = {"year": 2022}  # ERROR (missing required field title)

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

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

更多细节请参见 PEP 655。

(由 David Foster 和 Jelle Zijlstra 在 bpo-47087 中贡献,PEP 由 David Foster 编写)

PEP 673:Self 类型

新的 Self 注解提供了一种简单而又直观的方法来标注返回其类实例的方法。这一注解的行为与 PEP 484 中指定的基于 TypeVar 的方法是一致的,但更简洁、更易于遵循。

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

class MyLock:
    def __enter__(self) -> Self:
        self.lock()
        return self

    ...

class MyInt:
    @classmethod
    def fromhex(cls, s: str) -> Self:
        return cls(int(s, 16))

    ...

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

更多细节请参见 PEP 673。

(由 James Hilton-Balfe 在 bpo-46534 中贡献,PEP 由 Pradeep Kumar Srinivasan 和 James Hilton-Balfe 编写)

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

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

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

def run_query(sql: LiteralString) -> ...
    ...

def caller(
    arbitrary_string: str,
    query_string: LiteralString,
    table_name: LiteralString,
) -> None:
    run_query("SELECT * FROM students")       # ok
    run_query(query_string)                   # ok
    run_query("SELECT * FROM " + table_name)  # ok
    run_query(arbitrary_string)               # type checker error
    run_query(                                # type checker error
        f"SELECT * FROM students WHERE name = {arbitrary_string}"
    )

请参阅 PEP 675 了解详情。

(由 Jelle Zijlstra 在 bpo-47088 中贡献,PEP 由 Pradeep Kumar Srinivasan 和 Graham Bleaney 编写)

PEP 681:数据类变换

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

例如:

## The create_model decorator is defined by a library.
@typing.dataclass_transform()
def create_model(cls: Type[T]) -> Type[T]:
    cls.__init__ = ...
    cls.__eq__ = ...
    cls.__ne__ = ...
    return cls

## The create_model decorator can now be used to create new model classes:
@create_model
class CustomerModel:
    id: int
    name: str

c = CustomerModel(id=327, name="Eric Idle")

更多细节请参见 PEP 681。

(由 Jelle Zijlstra 在 gh-91860 中贡献,PEP 由 Erik De Bonte 和 Eric Traut 编写)

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

原计划随 Python 3.10 发布的 PEP 563 延迟注解求值(from __future__ import annotations 的 future 语句)已被无限期搁置。 更多信息请参见 指导委员会(Steering Council)邮件列表中的讨论 。

其他语言特性修改

其他 CPython 实现的改变

  • 实现了用于 complex 的 complex() 和用于 bytes 的 bytes() 特殊方法以支持 typing.SupportsComplex 和 typing.SupportsBytes 协议。 (由 Mark Dickinson 和 Donghee Na 在 bpo-24234 中贡献。)

  • 添加了新的内部哈希算法 siphash13。它与 siphash24 有类似的安全特性,但是对于长输入,它的速度略快。str、bytes 和其他一些类型现在使用它作为 hash() 的默认算法。PEP 552 基于哈希的 .pyc 文件 现在也使用 siphash13。(由 Inada Naoki 在 bpo-29410 中贡献)

  • 当使用没有参数的 raise 语句重新引发活动的异常时,被附加在此异常上的回溯现在始终为 sys.exc_info()[1].__traceback__。这意味着在当前 except 子句中对回溯的修改将被反映到重新引发的异常。(由 Irit Katriel 在 bpo-45711 中贡献)

  • 解释器状态对已处理异常(又名 exc_info 或 _PyErr_StackItem )的表示现在只有 exc_value 字段;exc_type 和 exc_traceback 已被移除,因为它们可以派生自 exc_value 。(由 Irit Katriel 在 bpo-45711 中贡献)

  • WIndows 安装程序添加了一个新的 命令行选项 AppendPath。它的行为类似于 PrependPath,但是会追加安装和脚本目录而不是前加。(由 Bastian Neuburger 在 bpo-44934 中贡献)

  • 为了使用 PyConfig.module_search_paths 初始化 sys.path,PyConfig.module_search_paths_set 字段现在必须使用1 作初始化,否则,该初始化行为会重新计算路径并替换任何加入到 module_search_paths 的值。

  • --help 选项的输出现在将适应于50行/80列。有关 Python environment variables 和 -X 选项的信息可以分别使用 --help-env 和 --help-xoptions 标志获得,并可以使用新的标志 --help-all。(由 Éric Araujo 在 bpo-46142 贡献。)

  • 使用 2 (二进制), 4, 8 (八进制), 16 (十六进制) 或 32 以外的基数例如以 10 (十进制) 为基数在 int 和 str 之间进行转换现在如果字符串表示形式中的位数超过特定限制则会引发 ValueError 以避免因算法复杂度导致的拒绝服务攻击风险。 这是对于 CVE 2020-10735 的一种缓解方案。 此限制可通过环境变量、命令行旗标或 sys API 来配置或者禁用。 参见 整数字符串转换长度限制 文档。 字符串形式的默认限制为 4300 位数字。

新增模块

改进的模块

asyncio

contextlib

  • 增加了非并行安全的 chdir() 上下文管理器用来改变当前工作目录并在退出时恢复它。 是 chdir() 的简单包装器。 (由 Filipe Laíns 在 bpo-25625 中贡献))

dataclasses

  • 修改了字段默认的可变性检查,默认仅允许 hashable 而非任何不为 dict, list 或 set 实例的对象。 (由 Eric V. Smith 在 bpo-44674 中贡献。)

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.RegexFlag 的 ASCII 成员而不是 'RegexFlag.ASCII'。

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

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

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

fcntl

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

fractions

  • 支持基于字符串执行 PEP 515 网络的 Fraction 初始化。 (由 Sergey B Kirpichev 在 bpo-44258 中贡献。)

  • Fraction 现在实现了一个 __int__ 方法,因而 isinstance(some_fraction, typing.SupportsInt) 检测将会通过。 (由 Mark Dickinson 在 bpo-44547 中贡献。)

functools

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: int | float, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> from typing import Union
>>> @fun.register
... def _(arg: Union[list, set], verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)
...

(由 Yurii Karabas 在 bpo-46014 中贡献。)

gzip

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

hashlib

  • hashlib.blake2b() 和 hashlib.blake2s() 现在将优先使用 libb2 而不是 Python 自带的副本。 (由 Christian Heimes 在 bpo-47095 中贡献。)

  • 包含 SHA3 和 SHAKE 的内部 _sha3 模块现在会使用 tiny_sha3 而不是 Keccak Code Package 来减小代码和二进制文件的大小。 hashlib 模块将首选来自 OpenSSL 的优化版 SHA3 和 SHAKE 实现。 这个改变将只影响不带 OpenSSL 支持的安装版。 (由 Christian Heimes 在 bpo-47098 中贡献。)

  • 增加了 hashlib.file_digest(),一个针对文件或文件型对象高效哈希运算的辅助函数。 (由 Christian Heimes 在 gh-89313 中贡献。)

IDLE 与 idlelib

  • 对 .pyi 文件应用语法高亮。 (由 Alex Waygood 和 Terry Jan Reedy 在 bpo-45447 中贡献。)

  • 当附带输入和输出地保存 Shell 时将包括提示符。 (由 Terry Jan Reedy 在 gh-95191 中贡献。)

inspect

(由 Pablo Galindo 在 gh-88116 中贡献。)

locale

logging

math

  • 增加了 math.exp2(): 返回 2 的 x 次幂。 (由 Gideon Mitchell 在 bpo-45917 中贡献。)

  • 增加了 math.cbrt(): 返回 x 的立方根。 (由 Ajith Ramachandran 在 bpo-44357 中贡献。)

  • 两个 math.pow() 边界情况的行为已改变,以便与 IEEE 754 规范保持一致。 math.pow(0.0, -math.inf) 和 math.pow(-0.0, -math.inf) 等运算现在将返回 inf。 在此之前它们会引发 ValueError。 (由 Mark Dickinson 在 bpo-44339 中贡献。)

  • 现在 math.nan 值将总是可用。 (由 Victor Stinner 在 bpo-46917 中贡献。)

operator

  • 增加了一个新函数 operator.call,使得 operator.call(obj, *args, **kwargs) == obj(*args, **kwargs)。 (由 Antony Lee 在 bpo-44019 中贡献。)

os

  • 在 Windows 上,os.urandom() 现在将使用 BCryptGenRandom(),而不是已被弃用的 CryptGenRandom()。 (由 Donghee Na 在 bpo-44611 中贡献。)

pathlib

re

  • 正则表达式现已支持原子化分组 ((?>...)) 和占有型数量限定符 (*+, ++, ?+, {m,n}+)。 (由 Jeffrey C. Jacobs 和 Serhiy Storchaka 在 bpo-433030 中贡献。)

shutil

socket

  • 为 NetBSD 添加了 CAN Socket 支持。 (由 Thomas Klausner 在 bpo-30512 中贡献。)

  • create_connection() 具有一个在连接失败的情况下引发包含所有错误而的 ExceptionGroup 不是只引发最后的错误的选项。 (由 Irit Katriel 在 bpo-29980 中贡献。)

sqlite3

string

sys

  • sys.exc_info() 的 type 和 traceback 字段现在是派生自 value (异常实例),因此当一个异常在处理期间被修改时,其变化会在后续对 exc_info() 的调用结果中反映出来。 (由 Irit Katriel 在 bpo-45711 中贡献。)

  • 增加了返回激活的异常实例的 sys.exception() (等价于 sys.exc_info()[1])。 (由 Irit Katriel 在 bpo-46328 中贡献。)

  • 增加了 sys.flags.safe_path 旗标。 (由 Victor Stinner 在 gh-57684 中贡献。)

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 中贡献。)

(本文超出站点单文档体积上限,已按小节截断)