Python 3.12 新版本特性速览 – wiki基地


Python 3.12:更快、更清晰、更安全的又一次飞跃

Python,这门以其简洁优雅和强大功能而闻名于世的编程语言,在不断地演进与完善。时隔一年,我们迎来了它的最新重要版本——Python 3.12。这个版本并非革命性的颠覆,而是在前几个版本(特别是 3.11 在性能上的重大突破)的基础上,进一步打磨语言的各个角落,带来了显著的性能提升、更清晰的语法、更强大的类型提示以及对开发者体验的诸多改进。

Python 3.12 的发布,是 Faster CPython 项目持续推进的成果,同时吸收了来自社区的大量建议和贡献。它在保持 Python 核心优势的同时,努力解决一些长期存在的痛点,让 Python 在面对现代软件开发挑战时更加游刃有余。

本文将深入探讨 Python 3.12 带来的主要新特性和改进,带您速览这次重要的版本升级。

一、性能飞跃:更快是永恒的追求

在 Python 3.11 中,我们见证了 CPython 解释器历史上最显著的性能提升之一。Python 3.12 在此基础上,继续优化,尤其是在特定场景下带来了激动人心的性能改进。根据官方 benchmarks,Python 3.12 比 Python 3.11 平均快 5% 左右,相比 Python 3.10 更是快了 22%。这些提升主要得益于以下几个关键的 PEP:

1. PEP 684: Per-Interpreter GIL (每解释器 GIL)

这无疑是 3.12 版本中最具战略意义的性能改进之一。长期以来,CPython 的全局解释器锁(GIL)一直是限制其在多核处理器上进行真多线程并行计算的主要瓶颈。虽然 Python 的 threading 模块可以用于并发,但由于 GIL 的存在,同一时刻只有一个线程能在 CPython 解释器中执行字节码,这使得 CPU 密集型任务无法充分利用多核优势。

PEP 684 并没有彻底移除 GIL(那将是一个更为庞大和复杂的工程),但它引入了一个关键的概念:每解释器 GIL。这意味着 CPython 解释器现在可以创建多个独立的子解释器(sub-interpreters),而每个子解释器都有自己的 GIL。不同子解释器中的 Python 代码可以在不同的 CPU 核上并行运行,即使它们都在执行 CPU 密集型任务。

这开启了 Python 在多核并行方面的新篇章。设想一下,一个大型应用可以拆分成多个相对独立的模块,每个模块运行在一个子解释器中。这些子解释器可以真正在多核上并行执行,极大地提高了整体吞吐量,特别是在构建高性能服务器、并行计算框架或需要隔离不同任务的环境时。

新的 cpython.create_interpreter API 允许程序显式创建和管理子解释器。虽然这个功能目前可能更多地面向库和框架开发者,而非普通应用程序开发者直接使用,但它为未来的并行计算库和异步框架提供了强大的底层支持。

需要注意的是,这个特性并不是“移除了 GIL”,主解释器依然有其 GIL。它解决的是多解释器场景下的并行问题,而不是单个解释器内多线程的并行问题。但这仍然是朝着更高效利用现代硬件迈出的重要一步。

2. PEP 709: Inlined Comprehensions (内联生成式)

在 Python 3.12 之前,列表推导式、字典推导式和集合推导式(Comprehensions)以及生成器表达式在幕后都是通过创建并调用一个临时的函数对象来实现的。例如,[x*2 for x in range(10)] 会编译成类似于调用一个内部函数,该函数包含循环和列表构建逻辑。这种实现方式虽然干净,但带来了函数调用的开销。

PEP 709 改变了这一点。在 Python 3.12 中,许多推导式和生成器表达式现在可以被“内联”(inlined)。这意味着它们的字节码会被直接编译到包含它们的父作用域中,而不是生成一个新的函数对象并调用它。

内联推导式的好处是显而易见的:
* 减少函数调用开销: 移除了创建和调用临时函数的步骤,从而提高了执行速度。
* 简化堆栈帧: 运行时堆栈更浅,对调试和分析也有一定帮助。

但这并不是一个完全透明的改变。一个值得注意的副作用是,内联推导式中的循环变量可能会泄漏到父作用域。在 Python 3.12 之前,推导式有自己的独立作用域,循环变量在推导式结束后就会消失。但在 3.12 中,如果推导式被内联,循环变量可能会保留其最后的值。例如:

“`python

Python 3.11 and earlier

x = ‘global’
result = [x for x in range(5)]
print(x) # Output: ‘global’

Python 3.12 (if inlined)

x = ‘global’
result = [x for x in range(5)]
print(x) # Output: 4
“`
(注意:实际是否泄漏取决于编译器是否选择内联该特定推导式,以及如何优化。但这改变了旧版本的确定行为。)

为了保持兼容性并避免意外的变量泄漏,编译器并非总是对推导式进行内联。例如,如果推导式中使用了复杂的表达式、嵌套了其他的推导式或生成器表达式,或者使用了异步推导式,它可能就不会被内联。尽管如此,对于大量常见的、简单的推导式场景,内联带来的性能提升是可观的。

3. PEP 688: pyexpat 支持缓冲协议

虽然不如前两个 PEP 影响广泛,但对于处理 XML 的应用来说,这是一个重要的性能改进。pyexpat 是 Python 的标准库 XML 解析模块,它现在支持缓冲协议(Buffer Protocol)。这意味着 pyexpat 在处理字节数据时,可以避免不必要的数据复制,直接操作底层缓冲区,从而提高了 XML 解析的效率,尤其是在处理大型 XML 数据时。这对于那些依赖 pyexpat 进行 XML 数据交换或处理的应用来说,是一个不错的加速。

4. 其他微优化

除了上述 PEP 带来的结构性变化,Python 3.12 还包含了 Faster CPython 项目组以及社区贡献者们进行的无数微小的优化,比如改进了对象创建、属性访问、垃圾回收等机制。这些累积起来的改进,共同构成了 3.12 在整体性能上的提升。

二、语法与类型提示:更清晰、更安全的代码

Python 的语法一直在不断地进化,以提高代码的可读性和表达力。Python 3.12 在这方面带来了几个重要的新特性,特别是在类型提示方面。

1. PEP 695: Type Parameter Syntax (新的类型参数语法)

类型提示是现代 Python 开发中不可或缺的一部分,它提高了代码的可维护性和可读性,并使得静态分析工具能够捕获潜在的错误。在定义泛型(Generic)类、函数或类型别名时,我们经常需要引入类型变量(TypeVar)、参数规范(ParamSpec)或类型变量元组(TypeVarTuple)。在 Python 3.11 及更早版本中,这通常需要单独的语句来定义这些类型变量,然后再在类型提示中使用它们:

“`python

Python 3.11 and earlier

from typing import TypeVar, Generic, ParamSpec, TypeVarTuple, TypeAlias

T = TypeVar(‘T’)
P = ParamSpec(‘P’)
Ts = TypeVarTuple(‘Ts’)

class MyGenericClass(Generic[T]):

def my_generic_function(arg: T) -> T:

type MyTupleAlias[S] = tuple[S, S] # TypeAlias introduced in 3.10

Using ParamSpec

def my_decorated_function(args: P.args, *kwargs: P.kwargs) -> R: # R would also be a TypeVar

“`

PEP 695 引入了一种新的、更简洁、更 Pythonic 的语法来定义类型参数。现在,你可以在方括号 [] 中直接声明类型参数,类似于在函数定义中声明参数:

“`python

Python 3.12+

Defining a generic class

class MyGenericClass[T]:

Defining a generic function

def my_generic_functionT -> T:

Defining a type alias with parameters using the ‘type’ keyword (also PEP 695)

type MyTupleAlias[S] = tuple[S, S]

Using ParamSpec and TypeVarTuple with the new syntax

def my_variadic_functionP, *Ts, R -> tuple[Ts, R]:

``
(注意:这里的
PTs是在函数签名[P,
Ts, R]` 中声明的类型参数)

这个新语法带来了几个好处:
* 更简洁: 无需单独的 TypeVar(...) 等语句,减少了代码行数。
* 更清晰: 类型参数的范围(scope)明确地绑定到其声明所在的泛型对象(类、函数或类型别名)。
* 更本地化: 类型参数可以在其声明范围内直接使用,无需从全局或模块级别导入。

此外,PEP 695 还正式引入了 type 关键字用于定义类型别名,这是在 Python 3.10 中通过 TypeAlias 注解软引入的语法的进一步完善和推广。新的 type Name[Parameters] = Definition 语法更加直观,并且与新的类型参数语法完美结合。

这种新的类型参数语法极大地提高了带有复杂类型提示的代码的可读性,让泛型的使用变得更加自然和方便。

2. PEP 698: override Decorator (新的 override 装饰器)

在面向对象编程中,子类覆盖(override)父类的方法是一个非常常见的操作。然而,有时候开发者可能会因为拼写错误、参数签名不匹配或者父类方法名变更等原因,意外地创建了一个新方法而不是覆盖父类的方法。这种错误很难被静态分析工具或运行时发现,因为它在语法上是合法的。

为了解决这个问题,Python 3.12 引入了 typing.override 装饰器 (通过 PEP 698 实现)。这个装饰器用于显式地标记一个方法是用来覆盖父类同名方法的。

“`python
from typing import override

class Base:
def get_value(self) -> int:
return 0

class Derived(Base):
@override
def get_value(self) -> int: # Correctly overrides Base.get_value
return 1

class AnotherDerived(Base):
@override
def get_val(self) -> int: # Mypy (and other type checkers) will report an error here!
return 2 # Error: Method ‘get_val’ marked with @override does not override any method in base class(es)

class YetAnotherDerived(Base):
@override
def get_value(self, extra_arg: int) -> int: # Mypy will report an error here!
return 3 # Error: Signature of ‘get_value’ in ‘YetAnotherDerived’ incompatible with signature of ‘get_value’ in ‘Base’
“`

当一个方法被 @override 装饰时,静态类型检查工具(如 Mypy, Pyright 等)会检查父类中是否存在同名的可覆盖方法,并且签名是否兼容。如果不存在或者签名不兼容,类型检查器就会报告错误。

这个装饰器并不会改变方法的运行时行为,它完全是为了静态分析而设计的。它的主要价值在于:
* 提高代码安全性: 在开发早期就能捕获潜在的覆盖错误。
* 提高代码可读性: 清晰地表明了方法的意图是覆盖父类方法。
* 方便重构: 当父类方法签名改变或被移除时,类型检查器会及时地在使用了 @override 的子类中给出提示,帮助开发者进行必要的修改。

@override 装饰器是 Python 类型提示系统日益完善的又一体现,对于大型项目或有复杂继承关系的代码库来说,这是一个非常有用的工具。

3. PEP 692: 使用 **kwargs 标记 TypedDict 必选/可选字段

TypedDict 是 Python 中用于为字典结构提供类型提示的重要工具。在 Python 3.8 引入 TypedDict 时,可以通过 total=True (默认) 或 total=False 来标记一个 TypedDict 中的所有字段是全部必需的还是全部可选的。如果需要混合必选和可选字段,需要使用 RequiredNotRequired 注解(从 Python 3.8+ 的 typing_extensions 引入,并在 3.11 中进入标准库 typing)。

PEP 692 并没有改变 RequiredNotRequired 本身,但它使得在 TypedDict 中使用 **kwargs 展开语法时,类型检查器能够正确地理解哪些字段是必选的,哪些是可选的。这在处理部分更新或构造复杂字典时非常有用。

“`python
from typing import TypedDict, Required, NotRequired

class UserProfile(TypedDict, total=False): # Using total=False makes all fields optional by default
name: Required[str] # Explicitly marking ‘name’ as required
age: int # Still optional because total=False
email: NotRequired[str] # Explicitly marking ’email’ as not required (redundant with total=False, but shows intent)

Example Usage

def update_profile(profile: UserProfile):

Valid calls

update_profile({‘name’: ‘Alice’})
update_profile({‘name’: ‘Bob’, ‘age’: 30})
update_profile({‘name’: ‘Charlie’, ‘age’: 25, ’email’: ‘[email protected]’})

Invalid call (missing required ‘name’) – Type checker error in 3.12+

update_profile({‘age’: 40})

``
(注意:这个 PEP 的核心在于让静态类型检查器在处理涉及 TypedDict 和
**kwargs的复杂场景时更加智能和准确,而Required/NotRequired` 语法本身在 3.11 就已经可用了。)

4. F-strings 解析改进

Python 的 f-strings (f"...") 是格式化字符串的强大工具。在 Python 3.12 之前,f-string 的解析器有一些限制,例如不能在 f-string 的表达式部分使用反斜杠 (\) 或注释 (#)。这些限制有时候会使得在 f-string 中嵌入复杂的表达式变得困难或需要变通。

Python 3.12 放宽了这些限制,使得 f-string 的解析更加灵活。现在,你可以在 f-string 的表达式部分使用反斜杠(例如,用于转义引号)和注释:

“`python

Python 3.12+

user_name = “Alice”
message = f”Hello, {user_name.upper()}\n” # Backslash inside the expression part is now fine
print(message)

data = {‘key’: ‘value’}
debug_str = f”Data: {data[‘key’]} #{‘accessing key’}” # Comment inside the expression part is now fine (though might be confusing)
print(debug_str)

Complex example with backslashes

path = “C:\Program Files”
print(f”Path: {path.replace(‘\’, ‘/’)}”) # Using backslash in a method call argument inside f-string
``
这些改进使得 f-string 在处理更复杂的表达式时更加自然,减少了使用
.format()` 或其他字符串操作方法的必要性。

三、标准库的增强与优化

Python 的标准库是其强大生态系统的重要组成部分。Python 3.12 对标准库也进行了一系列的增强和优化。

1. asyncio 模块改进

asyncio 是 Python 中进行异步编程的核心库。Python 3.12 为 asyncio 带来了更方便的超时处理机制:

  • asyncio.timeout(delay): 创建一个上下文管理器,可以用来对一段异步代码设置超时。如果代码块在指定的时间内未能完成,将会抛出 TimeoutError
  • asyncio.timeout_at(when): 类似于 asyncio.timeout,但是允许你指定一个具体的绝对时间点(作为 loop.time() 的值或 time.monotonic() 的值)作为超时截止时间。

这些新的上下文管理器提供了比之前使用 asyncio.wait_for 更简洁、更 Pythonic 的方式来处理超时,特别是在需要对一个代码块而不是单个协程设置超时时。

“`python
import asyncio

async def fetch_data():
await asyncio.sleep(5) # Simulate a long-running task
return “Data fetched”

async def main():
try:
# Set a 3-second timeout for the task
async with asyncio.timeout(3):
result = await fetch_data()
print(result)
except asyncio.TimeoutError:
print(“Operation timed out!”)

asyncio.run(main())
“`
这种语法更加清晰地表达了“对这段代码设置超时”的意图。

2. dataclasses 模块改进

dataclasses 是 Python 3.7 引入的用于简化数据类创建的工具。在 Python 3.12 中,使用 frozen=True 创建的不可变数据类的实例现在支持 replace() 方法。这意味着你可以在不改变原始实例的情况下,创建一个具有部分更新字段的新实例,即使原始实例是不可变的。

“`python
from dataclasses import dataclass, replace

@dataclass(frozen=True)
class Point:
x: int
y: int

p1 = Point(1, 2)

p1.x = 5 # Error: cannot assign to field ‘x’

p2 = replace(p1, x=5) # Now possible for frozen dataclasses
print(f”p1: {p1}”) # Output: p1: Point(x=1, y=2)
print(f”p2: {p2}”) # Output: p2: Point(x=5, y=2)
``
这个改进提高了 frozen dataclasses 的可用性,使其在某些场景下替代
namedtuple` 或手动实现的不可变类更加方便。

3. unittest 模块改进

unittest 是 Python 的单元测试框架。Python 3.12 引入了一个新的命令行选项 --durations,可以显示执行测试用例所需的时间,并按时间排序,这有助于识别慢速测试。此外,unittestsubTest 中也得到了一些改进,使其在循环中进行子测试时更易用。

4. sys.monitoring API (PEP 669)

Python 3.12 引入了低开销的运行时监控 API (sys.monitoring)。这个 API 允许开发者或工具(如调试器、性能分析器、覆盖率工具等)注册回调函数,以在解释器执行的关键事件发生时(例如,函数调用、返回、行更改、异常抛出等)接收通知。与传统的基于 sys.settracesys.setprofile 的方法相比,新的 API 设计目标是具有更低的性能开销,因为它允许更精细地控制监听的事件和对象。

这个 API 主要面向需要深度集成到 Python 运行时以提供服务的高级工具开发者。

5. 其他库的改进

  • inspect 模块:增强了对异步生成器(async generators)和异步上下文管理器(async context managers)的内省能力。
  • pathlib 模块:增加了对 tar 文件系统路径(tar file system paths)的支持。
  • sqlite3 模块:支持创建和使用 SQLite 的向量扩展(vector extension)。
  • 类型提示相关的改进:除了 PEP 695 和 PEP 698,typing 模块本身也有一些内部优化和改进,以更好地支持新的语法和特性。

四、C API 的清理与稳定

Python 的 C API 允许 C 或 C++ 代码与 Python 解释器交互,是构建高性能扩展模块或将 Python 嵌入其他应用的基础。随着 Python 语言和解释器的发展,C API 也在不断演变,有时会积累一些过时或不够清晰的接口。

Python 3.12 在 C API 方面的主要工作集中在清理和稳定

  • PEP 683: Immortal Objects (不朽对象): 引入了一种新的对象生命周期管理机制,旨在减少引用计数的开销。某些长期存在的对象(如 None, True, False, 小整数等)现在可以被标记为“不朽”,从而无需进行引用计数操作,这进一步提高了性能。
  • PEP 697: Limited C API 的改进: Limited C API 是一个旨在提供一个更稳定、更长期兼容的 C API 子集,使得用它编写的扩展模块在未来 Python 版本中无需重新编译就能工作。Python 3.12 对 Limited C API 进行了改进,增加了更多可用的接口,并提高了其可用性。
  • 移除过时的函数: 移除了许多在之前版本中已经被标记为废弃(deprecated)的 C API 函数,鼓励扩展开发者迁移到更现代的接口。

这些 C API 的改进对于依赖 C 扩展的库(如 NumPy, SciPy, TensorFlow, PyTorch 等)至关重要,有助于提高它们的稳定性和在未来 Python 版本上的兼容性。对于 CPython 开发者来说,API 的清理也使得解释器内部结构更加清晰。

五、移除和废弃

每个 Python 版本都会移除一些过时或不再推荐使用的功能,并标记一些功能在未来版本中废弃。Python 3.12 也不例外。

  • 移除 distutils 模块: distutils 是 Python 最初用于构建和安装包的工具,但已经被更现代、功能更丰富的 setuptoolsbuild 等工具取代。在 Python 3.10 中 distutils 被标记为废弃,并在 Python 3.12 中被彻底移除。开发者应该迁移到 PEP 517 (build backend) 兼容的构建工具。
  • 废弃一些旧的 unittest 别名: 一些为了向后兼容更老版本的 Python 而存在的 unittest 模块中的别名被标记为废弃,并将在未来版本中移除。

这些移除和废弃旨在清理语言和标准库,减少维护负担,并引导开发者使用更现代和推荐的方式。

六、总结与展望

Python 3.12 是 Python 语言发展历程中的又一个重要里程碑。它在 Python 3.11 奠定的性能基础上,通过 PEP 684 (每解释器 GIL) 和 PEP 709 (内联推导式) 等特性,进一步推动了性能的提升。同时,通过 PEP 695 (新的类型参数语法) 和 PEP 698 (@override 装饰器),极大地增强了类型提示的表达力和可用性,使得编写类型安全的代码更加容易和愉快。其他改进,如增强的 f-strings、更友好的 asyncio 超时处理、改进的 dataclasses 以及标准库的各种优化,共同提升了开发者的体验。

C API 的清理和稳定工作,虽然普通用户感知不强,但对于整个 Python 生态系统的健康发展至关重要,它为底层库和框架提供了更坚实的基础。

对于大多数 Python 开发者来说,升级到 Python 3.12 将是平滑的,并且能立即享受到性能提升和一些语法便利。特别是对于大型项目,新的类型提示语法和 @override 装饰器将显著提高代码的可读性和可维护性。对于需要处理并发或并行计算的场景,虽然每解释器 GIL 仍处于早期阶段,但它为未来的发展打开了新的可能性。

Python 社区并没有停止前进的脚步。Faster CPython 项目仍在继续,未来的版本有望带来更多性能上的突破。同时,类型提示系统也将持续演进,新的语言特性也在社区中积极讨论和提案中。

Python 3.12 是对这门语言的又一次精雕细琢,它使得 Python 在追求易学易用的同时,不忘提升性能和适应现代软件开发的复杂需求。是时候升级您的 Python 环境,体验这些令人兴奋的新特性了!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部