就在六月份,Python官方发布了这个言语的3.12预发版别。像平常一样,这个版别更新了很多内容,我也选择了大部分内容翻译了一下,先一睹为快吧。

改进报错信息

来自官方标准库的模块现在能够在报NameError时提示问题原因,比方

>>> sys.version_info
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'sys' is not defined. Did you forget to import 'sys'?

当实例内报NameError时也会提示问题原因,比方成员办法引用了未界说的变量,而这个变量名又和成员变量名相同时会提示用户是否忘记加self,比方

>>> class A:
...    def __init__(self):
...        self.blech = 1
...
...    def foo(self):
...        somethin = blech
>>> A().foo()
Traceback (most recent call last):
  File "<stdin>", line 1
    somethin = blech
               ^^^^^
NameError: name 'blech' is not defined. Did you mean: 'self.blech'?

当导入模块时importfrom写反报SyntaxError时也会提示原因,比方

>>> import a.y.z from b.y.z
Traceback (most recent call last):
  File "<stdin>", line 1
    import a.y.z from b.y.z
    ^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Did you mean to use 'from ... import ...' instead?

在导入模块过错报ImportError时也会提示应该导入哪个目标,比方

>>> from collections import chainmap
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'chainmap' from 'collections'. Did you mean: 'ChainMap'?

新功能

PEP701 f-string的语义格式化

现在f-string内的表达式可所以任何合法的Python表达式,包括反斜杠、Unicode转义、多行表达式、注释和重复运用的引号品种。

  • 重复运用的引号品种:在Python 3.12版别中,用户能够在表达式里重复运用f-string运用过的引号品种,比方这里重复运用了双引号
>>> 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'

因为这个改动,现在Python能够内嵌多个f-string了,比方曾经只能内嵌4层

>>> f"""{f'''{f'{f"{1+1}"}'}'''}"""
'2'

现在则没有这种约束

>>> f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
'2'
  • 多行表达式和注释:曾经在f-string中必须把表达式写在一行内,可读性不高。现在没有这种约束,而且能够包括注释。
>>> 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'
  • 反斜杠和Unicode字符:曾经在f-string中不能运用反斜杠和Unicode转义。这会使得有的Unicode字符无法在f-string中运用,现在没有这种约束了。
>>> 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

一个有用的副作用就是现在报错的位置更精准了。比方在3.11中解说器不能告知用户SyntaxError过错产生的位置。

>>> my_string = f"{x z y}" + f"{1 + 1}"
  File "<stdin>", line 1
    (x z y)
     ^^^
SyntaxError: f-string: invalid syntax. Perhaps you forgot a comma?

现在能够获取到报错位置了。

>>> my_string = f"{x z y}" + f"{1 + 1}"
  File "<stdin>", line 1
    my_string = f"{x z y}" + f"{1 + 1}"
                   ^^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?

PEP709 内联行为

现在解说器会把字典、列表和集合了解成内联行为,而不是只用一次的数据结构,这样做会让程序运转速度提升一倍。可是,迭代器并不会了解成内联行为。

译者:这部分关系到虚拟机帧栈的优化,其API对普通用户并无感知。

PEP 688 Buffer协议能够暴露给用户运用

这个提案介绍了如何让Python用户运用到buffer协议,只需求让类完成__buffer__()办法就能够把它当作Buffer类运用。

与此同时,新的collections.abc.Buffer笼统基类供给了标准的暴露buffer的方式,比方在类型注解里。在新的inspect.BufferFlags枚举能够表示自界说的buffer资源。

与类型注解相关的新功能

PEP 692 用TypedDict注解**kwargs类型

PEP 484 介绍了如何注解函数签名中**kwargs的类型,可是所有的**kwargs类型都一样。这份提案供给了一种更准确的类型注解计划,比方

from typing import TypedDict, Unpack
class Movie(TypedDict):
  name: str
  year: int
def foo(**kwargs: Unpack[Movie]): ...

PEP 698 静态类型注解的override装饰器

typing模块中参加了一个新的装饰器typing.override(),它表明被它润饰的办法需求复写其父类的同名办法(译者注:类似于Java的@Overide)。它能够让类型检查该办法是否正确复写了父类的办法。

 from typing import override
class Base:
  def get_color(self) -> str:
    return "blue"
class GoodChild(Base):
  @override  # ok: overrides Base.get_color
  def get_color(self) -> str:
    return "yellow"
class BadChild(Base):
  @override  # type checker error: does not override Base.get_color
  def get_colour(self) -> str:
    return "red" 

PEP 695 参数类型语法

在PEP 484 中,Python对泛型类和办法类型注解的支撑有点啰嗦且不够准确,并需求一套更直白的类型声明计划。本提案引入了一种新的、简洁的、直白的类型注解计划。

def max[T](args: Iterable[T]) -> T:
    ...
class list[T]:
    def __getitem__(self, index: int, /) -> T:
        ...
    def append(self, element: T) -> None:
        ...

此外,本计划还引入了一种新type别号声明计划,并能够经过type创建一个TypeAliasType实例。

type Point = tuple[float, float]

类型别号相同能够参加泛型。

type Point[T] = tuple[T, T]

新的语法规则答应声明TypeVarTupleParamSpec,就像声明TypeVar一样。

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  

类型别号、规模以及约束类型只要在解说器需求的时分创建,也就是说别号能够在代码其他地方被重写。

参数类型的声明作用于声明的规模,对其外部是不收效的。举个例子,函数参数的类型注解能够作用于其派生类的办法或该类的其他地方。然而,它不能作用于模块规模内的其他地方,即使这个地方位于该类的界说的后边。具体运用办法能够参考Type parameter lists章节。

为了支撑这种规模的类型注解,现在虚拟机引入了一种新的规模——注解规模(annotation scope)。在大多数情况下,这个规模等同于函数的规模,可是它会和不同的类的规模产生关联。在Python 3.13中,所有的类型注解都会在这个规模内。

其他言语改动(部分)

  • 增加了环境变量PYTHONPERFSUPPORT、命令行参数-X perf以及APIsys.activate_stack_trampoline()sys.deactivate_stack_trampoline()sys.is_stack_trampoline_active()以支撑Linux优化(Python support for the Linux perf profiler)
  • 如果底层字典结构是可哈希的,那么types.MappingProxyType实例现在也是可哈希的
  • 语法剖析器现在能够剖析空字节
  • 现在GC只会在字节码之间的暂停点运转,而不是分配内存的时分运转。另外,GC还会在调用PyErr_CheckSignals()时运转。这样,在Python的C扩展中解说器能够履行很多的C言语代码而不去履行Python代码,以便削减GC运转的可能性。
  • 你能够在生成的数据中运用海象运算符(:=)来赋值,比方[(b := 1) for a, b.prop in some_iter]
  • slice目标现在是可哈希的,所以能够用作字典的键。
  • sum()办法现在用了新的求和算法,所以现在更准确了。

小结

以上是Python 3.12语法层面的改动。能够看出,现在Python委员会的发力点一个是类型注解,另一个是GC的运用效率。他们这么做也很好了解,这两个问题一直是为人诟病的症结。弱类型言语使得Python成为不了大型项目的开发言语,而效率低下的GC也是阻碍Python往前一步的绊脚石。

不管怎么说,Python毫无疑问是当下最热门的言语,具领会往什么方向开展,让我们拭目以待吧。