当前位置:   article > 正文

Python 3.12 到底更新了啥?_python 3.12新功能

python 3.12新功能

1. 写在前面

2023年10月2日,Python 最新的稳定版本 3.12 正式发布。这个新版本带来了一系列的新功能,让大多数Python 开发者感到兴奋。

接下去让我们花费几分钟时间,阅读有关最新版本 Python 3.12 更新的信息;

公众号: 滑翔的纸飞机

2. Python 3.12

此版本的主要更改如下:

  • 更灵活的 f-string 解析,允许许多以前不允许的语法(PEP 701);
  • 支持具有单独全局解释器锁的独立子解释器(PEP 684);
  • 引入在 Python 代码中使用缓冲协议的方法 (PEP 688);
  • 引入新的 debugging/profiling API(PEP 669);
  • 性能优化,如 PEP 709 和对 BOLT 二进制优化器的支持,估计总体性能提高了 5%;
  • 改进错误信息提示;
  • 支持 Linux perf 分析器在跟踪过程中报告 Python 函数名称;

类型注释:

  • 为泛型类引入新的类型注解语法(PEP 695);
  • 为方法引入新的 override 装饰器(PEP 698);

接下来,本文将简要介绍值得关注的变化。

2.1 PEP 701:f-string 语法

解除了对 f-string 使用的一些限制。现在,f-string 中的表达式组件可以是任何有效的 Python 表达式,包括与包含 f-string 的字符串重复使用相同引号的字符串、多行表达式、注释、反斜线和 unicode 转义序列。

  • 引号重用
    在 Python 3.11 中,重用与外层 f-string 相同的引号会引发语法错误,迫使用户使用其他可用的引号(例如,如果 f-string 使用单引号,则单引号内需要使用双引号或三引号)。在 Python 3.12 中,现在可以这样做了:

    >>> 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'
    
    • 1
    • 2
    • 3

    Python 3.11:
    无法任意嵌套 f-string,允许最多嵌套的 f-string:

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

    Python 3.12:
    f-strings 可以在表达式组件中包含任何有效的 Python 表达式,因此可以任意嵌套 f-strings:

    >>> f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
    
    • 1

‘2’
```

  • 多行表达式和注释
    在 Python 3.11 中,f-string 表达式必须定义在一行中,即使 f-string 中的表达式可以跨多行。在 Python 3.12 中,现在可以定义跨多行的 f-string 并添加内联注释:

    >>> f"This is the playlist: {", ".join([
    
    • 1

… ‘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 字符
    在 Python 3.12 之前,f-string 表达式不能包含 unicode 转义序列和反斜杠,现在,你可以定义这样的表达式:\N{snowman}\N

    >>> 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 更准确的消息提示
    现在 f-string 的错误消息更精确,同时包括错误的确切位置。例如:

    在 Python 3.11:

    >>> my_string = f"{x z y}" + f"{1 + 1}"
      File "<stdin>", line 1
        (x z y)
           ^
    SyntaxError: f-string: invalid syntax
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在 Python 3.12 中:

    >>> 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?
    
    • 1
    • 2
    • 3
    • 4
    • 5

请参见 PEP 701

2.2 PEP 684: Per-Interpreter GIL

简单来讲,GIL(全局解释器锁)是一个互斥锁,它只允许一个线程控制 Python 解释器(某个线程想要执行,必须要先拿到 GIL ,在一个 python 解释器里面,GIL 只有一个,拿不到 GIL 的就不允许执行),这就意味着即使你在 Python 中创建多个线程,也只会有一个线程在运行。

随着 “Per-Interpreter GIL” 的引用,单个 python 解释器不再共享同一个 GIL。这种隔离级别允许每个子 python 解释器真正地并发运行。

这意味着我们可以通过生成额外的子解释器来绕过 Python 的并发限制,其中每个子解释器都有自己的GIL(拿到一个 GIL 锁)。

Python3.12 PEP 684 引入了每个解释器的 GIL,因此现在可以为每个解释器创建具有唯一 GIL 的子解释器。这样,Python 程序就能充分利用多个 CPU 内核。但目前只能通过 C-API 使用,不过预计 3.13 版将推出 Python API。

有关如何将 C-API 用于子解释器和每个解释器的 GIL 示例

请参见 PEP 648

2.3 PEP 688:支持 Python 代码 buffer(缓冲区) 协议

2.3.1 简单说明 buffer

在 Python 中,buffer 类型对象用于以面向字节的格式显示给定对象的内部数据。Python 对缓冲区(buffer)的主要用途是存储和操作巨大的数据数组并在不创建副本的情况下处理它们。

buffer 接口仅受 strings、Unicode、arrays 和 bytearrays 支持。

numpy 数组也在后台使用此接口。

我们可以在相同的 buffer 实例上工作,而无需使用 buffer 接口创建数据副本。

>>> import numpy as np
>>> arr = np.array([1, 2, 3])
>>> arr2 = np.asarray(arr)
>>> arr2[2] = 50
>>> print(arr, arr2)
[ 1  2 50] [ 1  2 50]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

输出:

[ 1  2 50] [ 1  2 50]
  • 1

在上面的例子中,我们创建了一个名为 arr 的 numpy 数组,并使用它创建另一个名为 arr2 的数组。

因为 numpy 模块支持缓冲区协议,并且使用数组的视图发送数据而不是生成新数组,所以更新数组 arr2 也会更新原始 arr。

在 Python 3 中,可以使用 memoryview() 函数,用于返回实现 buffer 接口的 memoryview 对象并创建支持该接口的对象的视图。

memoryview(): 返回给定参数的内存查看对象(memory view)。所谓内存查看对象,是指对支持缓冲区协议的数据进行包装,在不需要复制对象基础上允许 Python 代码访问。

>>> a = bytearray("Sample Bytes", "utf-8")
>>> m = memoryview(a)
>>> print(m[2], type(m), m)
109 <class 'memoryview'> <memory at 0x7faf72c7e7c0>
>>> print(m[0:6].tobytes(), type(m), m)
b'Sample' <class 'memoryview'> <memory at 0x7faf72c7e880>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

>>> print(m[0:6].tobytes(), type(m), m)

在这种情况下,缓冲区是一个子字符串,从位置0开始,长度为6,并且它不占用额外的存储空间,它引用字符串的一个片段。这对于像这样的短字符串不是很有用,但在使用大量数据时可能是必要的。

2.3.2 PEP 688:引入了一种在 Python 代码中使用缓冲协议的方法

PEP 688 引入了一种在 Python 代码中使用缓冲协议的方法。实现了 __buffer__() 方法的类现在可以作为缓冲区类型使用。

新的 collections.abc.Buffer ABC(抽象基类)提供了标准的暴露buffer的方式,比如在类型注解里。在新的inspect.BufferFlags枚举可以表示自定义的buffer资源。

请参见 PEP 688

2.4 PEP 669:CPython 低损耗监控

在 CPython 中使用 profilers 或 debuggers 会严重影响性能。速度降低一个数量级是很常见的。

PEP 669 提出了一个用于监控在 CPython 上运行的 Python 程序的 API,它能以低成本实现监控。

虽然这个PEP没有指定实现,但预计它将使用PEP 659的加速步骤来实现。将添加一个命名空间,它将包含相关的函数和常量。

请参见 PEP 669

2.5 性能优化:PEP 709:内联行为

字典、列表和集合推导式现在是内联的,而不是为每次推导式的执行创建一个新的一次性函数对象。这样可以将推导式的执行速度提高两倍。

请参见 PEP 709;

2.6 异常错误消息改进

  • NameError 异常改进

    官方标准库模块引发 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'?
    
    • 1
    • 2
    • 3
    • 4

    或者,当方法引发 NameError 时也会提示问题原因:

    >>> 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'?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • import 和 from 异常改进

    from y import x 写成 import x from y 触发 SyntaxError 时也会提示原因:

    示例:

    >>> import a.y.z from b.y.z
    
    • 1

Traceback (most recent call last):
File “”, line 1
import a.y.z from b.y.z
^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Did you mean to use ‘from … import …’ instead?


模块导入触发 ImportError 时也会提示应该导入哪个对象:

示例:

  • 1
  • 2
  • 3
  • 4
  • 5
>>> from collections import chainmap
  • 1

Traceback (most recent call last):
File “”, line 1, in
ImportError: cannot import name ‘chainmap’ from ‘collections’. Did you mean: ‘ChainMap’?


### 2.7 PEP 692:使用 TypedDict 进行更精确的 **kwargs 定义

目前,只要 `**kwargs` 指定的所有关键字参数都是同一类型,那么`**kwargs` 就可以进行类型提示。这种行为会有很大的局限性。因此,在本 PEP 中,提出了一种新方法来实现更精确的 `**kwargs` 类型化。通过使用 TypedDict 来包含不同类型关键字参数。

示例:

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

from typing import TypedDict, Unpack

class Movie(TypedDict):
name: str
year: int

def foo(**kwargs: Unpack[Movie]): …


示例:

  • 1
  • 2
  • 3

movie: dict[str, object] = {“name”: “Life of Brian”, “year”: 1979}
foo(**movie) # WRONG! Movie is of type dict[str, object]

typed_movie: Movie = {“name”: “The Meaning of Life”, “year”: 1983}
foo(**typed_movie) # OK!

another_movie = {“name”: “Life of Brian”, “year”: 1979}
foo(**another_movie) # Depends on the type checker.


请参见 [PEP 692](https://peps.python.org/pep-0692/);

### 2.8 PEP 698:覆盖静态类型的装饰器

类型模块中新增了一个装饰器 typing.override()。它向类型检查程序表明,该方法旨在覆盖超类中的方法。这样,子类计划覆盖基类中的某些方法,实际没有覆盖到,类型检查程序就能捕捉到错误。

示例:

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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 698](https://docs.python.org/3.12/whatsnew/3.12.html#pep-698-override-decorator-for-static-typing);

### 2.9 PEP 695:新类型参数语法

PEP 695 引入了一种新的、更紧凑的、更显式的方式来创建泛型类和函数:

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

def max[T](args: Iterable[T]) -> T:

class list[T]:
def getitem(self, index: int, /) -> T:

def append(self, element: T) -> None:
    ...
  • 1
  • 2

此外,还引入了一种使用 type 语句声明类型别名的新方法,即创建 TypeAliasType 的实例:

`type Point = tuple[float, float]`

类型别名也可以是泛型的:`type Point[T] = tuple[T, T]`

新语法允许声明 TypeVarTuple 和 ParamSpec 参数,以及带有边界或约束条件的 TypeVar 参数:

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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](https://peps.python.org/pep-0695/);

## 3.最后
更多内容请参阅[官网](https://docs.python.org/3.12/whatsnew/3.12.html#pep-695-type-parameter-syntax)。

#### 感谢您花时间阅读文章
#### 关注公众号不迷路



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/612489
推荐阅读
相关标签
  

闽ICP备14008679号