PEP 695 在 Python 3.12 中引入了一种新颖且更为清晰的方式来定义泛型类和函数,旨在提升类型参数的明确性和简洁性。这个提案不仅改善了类型系统的可读性,还增强了其功能性。以下是这些变化的详细概述:
在 PEP 484 的基础上,PEP 695 引入了一种更紧凑、明确的泛型类和函数的创建方法。与以前的详细语法相比,新方法更简洁,且类型参数的范围更加明确。例如:
def max[T](args: Iterable[T]) -> T:
...
class list[T]:
def __getitem__(self, index: int, /) -> T:
...
def append(self, element: T) -> None:
...
PEP 695 还引入了使用 type
语句声明类型别名的方法。这些别名可以是普通类型或泛型类型,为类型注解提供了更多的灵活性。例如:
type Point = tuple[float, float]
type Point[T] = tuple[T, T]
这个提案允许声明各种新的类型参数,例如 TypeVarTuple
和 ParamSpec
,以及带边界或约束的 TypeVar
。这些新的类型参数扩展了 Python 类型系统的表达能力。例如:
type IntFunc[**P] = Callable[P, int] # ParamSpec
type LabeledTuple[*Ts] = tuple[str, *Ts] # TypeVarTuple
type HashableSequence[T: Hashable] = Sequence[T] # TypeVar with bound
type IntOrStrSequence[T: (int, str)] = Sequence[T] # TypeVar with constraints
PEP 695 强调,类型别名的值和类型变量的边界、约束仅在需要时进行求值,即实现了惰性求值。这意味着类型别名可以引用稍后在文件中定义的其他类型。同时,通过类型参数列表声明的类型参数在其声明的作用域内及嵌套作用域内可见,但在外部作用域不可见。
为了支持这些作用域定义,PEP 695 引入了一种新的作用域——标注作用域。这种作用域在很大程度上类似于函数作用域,但其与封闭类作用域的交互方式有所不同。在未来的 Python 3.13 中,标注也将在这种新的标注作用域中进行求值。
通过这些改进,PEP 695 不仅提高了类型注解的可读性和易用性,还为 Python 类型系统带来了更强的表达力和灵活性。更多细节可以在 PEP 695 中找到。
Python 3.12 中的 PEP 701 带来了对 f-字符串(格式化字符串)的显著改进,放宽了之前的一些限制,使其更加强大和灵活。以下是这些改进的详细介绍:
在 Python 3.11 及之前版本中,f-字符串中不能使用与其本身相同的引号,否则会引发 SyntaxError
。例如,如果 f-字符串用单引号标记,则表达式部分不能包含单引号。这种限制在 Python 3.12 中被取消,现在你可以在 f-字符串的表达式部分自由使用任何类型的引号。例如:
songs = ['Take me back to Eden', 'Alkaline', 'Ascensionism']
f"This is the playlist: {', '.join(songs)}"
# 输出: 'This is the playlist: Take me back to Eden, Alkaline, Ascensionism'
此外,这项改变使得 f-字符串可以更灵活地嵌套使用,如:
f"{f'{f"{f"{f"{1+1}"}"}"}'}"
# 输出: '2'
在以前的版本中,f-字符串的表达式必须在一行内完成,这对于复杂的表达式或需要注释的情况不太方便。Python 3.12 允许 f-字符串表达式跨越多行,并支持在其中添加注释,使代码更易于理解和维护。例如:
f"""This is the playlist: {', '.join([
'Take me back to Eden', # My, my, those eyes like fire
'Alkaline', # Not acid nor alkaline
'Ascensionism' # Take to the broken skies at last
])}"""
# 输出: 'This is the playlist: Take me back to Eden, Alkaline, Ascensionism'
在 Python 3.12 之前,f-字符串表达式中不能包含任何反斜杠 (\
) 字符,这限制了 Unicode 转义序列的使用。现在,你可以在 f-字符串表达式中使用反斜杠和 Unicode 转义序列。例如:
songs = ['Take me back to Eden', 'Alkaline', 'Ascensionism']
print(f"This is the playlist: {'\n'.join(songs)}")
# 输出:
# This is the playlist: Take me back to Eden
# Alkaline
# Ascensionism
print(f"This is the playlist: {'\N{BLACK HEART SUIT}'.join(songs)}")
# 输出: 'This is the playlist: Take me back to Eden♥Alkaline♥Ascensionism'
总的来说,PEP 701 为 Python 开发者提供了更大的灵活性和表达能力,尤其是在处理复杂的字符串格式化时。这些改进使得 f-字符串成为一个更加强大和便捷的工具。
在 Python 3.11 及以前版本中,f-字符串的错误消息往往缺乏精确性。例如,在遇到语法错误时,Python 3.11 可能会给出如下错误提示:
my_string = f"{x z y}" + f"{1 + 1}"
# 错误输出:
# File "", line 1
# (x z y)
# ^^^
# SyntaxError: f-string: invalid syntax. Perhaps you forgot a comma?
在这种情况下,错误消息不仅没有准确指出错误的具体位置,而且错误的表达式被不自然地用括号括起来。
Python 3.12 使用 PEG 解析器来解析 f-字符串,使得错误消息变得更加精确和有用。现在,当遇到类似的语法错误时,错误消息会精确指出错误所在的位置,并显示整行代码:
my_string = f"{x z y}" + f"{1 + 1}"
# 错误输出:
# File "", line 1
# my_string = f"{x z y}" + f"{1 + 1}"
# ^^^
# SyntaxError: invalid syntax. Perhaps you forgot a comma?
PEP 684 在 Python 3.12 中引入了一个重要的更新——解释器级的全局解释器锁(GIL)。这项改进旨在解决 Python 在多核 CPU 性能方面的一些限制,为 Python 带来更好的并行计算能力。
GIL 是 Python 解释器中一个众所周知的特性,它在任何时候只允许一个线程执行 Python 字节码。虽然这简化了 CPython 解释器的设计并提高了单线程程序的性能,但它也限制了 Python 程序在多核处理器上的并行执行能力。
PEP 684 通过引入解释器级 GIL,使得可以创建带有独立 GIL 的子解释器。这意味着每个子解释器可以在其自己的线程中独立运行,从而更好地利用多核 CPU 的性能。
目前,这个特性只能通过 C-API 实现,预计在 Python 3.13 中将添加相应的 Python API。使用 Py_NewInterpreterFromConfig()
函数可以创建一个具有独立 GIL 的新解释器。以下是一个示例代码:
PyInterpreterConfig config = {
.check_multi_interp_extensions = 1,
.gil = PyInterpreterConfig_OWN_GIL,
};
PyThreadState *tstate = NULL;
PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config);
if (PyStatus_Exception(status)) {
return -1;
}
// 新解释器现在在当前线程中激活
更多关于如何使用 C-API 来操作子解释器和解释器级 GIL 的示例可以在 Modules/_xxsubinterpretersmodule.c 中找到。
总体而言,PEP 684 的引入为 Python 的并行处理和多核性能优化提供了一个重要的步骤,使 Python 在多核处理器上的应用变得更加高效。这一改进由 Eric Snow 贡献,并在 gh-104210 等项目中得以实现。
PEP 669 在 Python 3.12 中引入了一个为 CPython 设计的新 API,用于实现低影响的性能监控。这一提案的核心在于为性能分析器、调试器和其他监控工具提供了一个高效且对程序性能影响小的监控方式。
广泛的事件覆盖:该 API 能监控包括函数调用、返回、代码行执行、异常处理和跳转等在内的多种事件,为开发者提供全面的性能分析视角。
精确的性能开销控制:PEP 669 允许开发者只对他们需要监控的事件付出性能开销。这种选择性监控大大降低了对程序性能的影响,尤其是在生产环境中。
近零开销的调试和覆盖工具支持:通过这个 API,开发者可以使用几乎不影响程序性能的调试器和代码覆盖工具,这在以往的 Python 版本中是难以实现的。
这个新引入的 sys.monitoring
模块提供了访问和控制事件监控的接口。通过这个模块,开发者可以注册监控事件、控制监控的粒度和范围,以及收集和分析性能数据。具体使用方法和 API 文档可以在 sys.monitoring
中找到。
PEP 688 为 Python 3.12 带来了对缓冲区协议的重要改进,使得在 Python 代码中使用和实现缓冲区协议变得更加直接和易于管理。
缓冲区协议(buffer protocol)在 Python 中主要用于提供一种访问对象内存表示的机制。这个协议常见于需要高性能数据处理的场景,比如在处理大量数据或进行科学计算时。
PEP 688 引入了以下关键特性:
__buffer__()
方法: 类通过实现 __buffer__()
方法可以作为缓冲区类型使用。这允许自定义类直接支持缓冲区协议,从而与 Python 的内置缓冲区类型(如字节串和数组)一样,可以更有效地处理数据。
collections.abc.Buffer
抽象基类: 这个新引入的抽象基类定义了缓冲区对象应有的基本行为和接口。通过实现这个类,开发者可以确保他们的自定义缓冲区类型符合预期的标准,从而提高代码的可维护性和可读性。
inspect.BufferFlags
枚举: 这个新的枚举提供了一组标志,用于自定义缓冲区的创建过程。这些标志让开发者能够更精确地控制缓冲区的行为,如只读或只写访问,以及缓冲区的其他属性。
这些改进使得缓冲区协议在 Python 中更易于访问和使用,特别是对于需要高效数据处理的应用。它们还提高了与缓冲区相关的代码的可读性和可维护性,使得开发者能够更容易地实现和使用复杂的内存管理逻辑。这对于科学计算、数据分析、图像处理等领域的 Python 开发者来说,是一个重要的步骤。
Python 3.12 版本中包含了对错误消息和类型提示的重要改进,这些更新旨在提高代码的可读性和易用性。
更明确的 NameError
提示:
NameError
并传播到最高层级时,如果与标准库中的模块名称相关,错误消息会建议可能的导入。>>> sys.version_info
NameError: name 'sys' is not defined. Did you forget to import 'sys'?
针对实例的 NameError
改进:
NameError
,且实例具有与异常中名称相同的属性,则错误建议会包括 self.
。class A:
def __init__(self):
self.blech = 1
def foo(self):
somethin = blech
A().foo()
# NameError: name 'blech' is not defined. Did you mean: 'self.blech'?
改进的 SyntaxError
:
import x from y
语法时,会提示正确的 from y import x
形式。>>> import a.y.z from b.y.z
SyntaxError: Did you mean to use 'from ... import ...' instead?
更有帮助的 ImportError
:
>>> from collections import chainmap
ImportError: cannot import name 'chainmap' from 'collections'. Did you mean: 'ChainMap'?
PEP 692: TypedDict
的应用:
TypedDict
来更精确地注释 **kwargs
。from typing import TypedDict, Unpack
class Movie(TypedDict):
name: str
year: int
def foo(**kwargs: Unpack[Movie]):
...
PEP 698: typing.override
装饰器:
from typing import override
class Base:
def get_color(self) -> str:
return "blue"
class GoodChild(Base):
@override
def get_color(self) -> str:
return "yellow"
class BadChild(Base):
@override
def get_colour(self) -> str:
return "red"
# 'BadChild.get_colour' 引发类型检查器错误,因为它没有重写 'Base.get_color'
这些改进不仅提升了 Python 的错误消息的准确性和可理解性,还增强了类型提示的表达能力,使得 Python 代码更加健壮和易于维护。