我正在参与「启航方案」


去年(2021)的 10 月份,Python 发布了 3.10.0 正式版,我也在第一时间做了介绍(相关文章);一年后的几天前,Python 3.11.0 正式版也亮相了。现在的下载链接:www.python.org/downloads/r…
下面就让咱们看一下首要的新特性吧。

3.11 带来的最直观改变便是,Python 更快了。官方的说法是能带来 10%~60% 的提速,在基准测试上均匀达到了 1.22x。更细致的介绍请参见 What’s New In Python 3.11 — Python 3.11.0 documentation。

下面摘抄其中部分提升

操作 形式 限定 最大加快 贡献者(们)
二元运算 x+x;x*x;x-x; Binary add, multiply and subtract for common types such asint,float, andstrtake custom fast paths for their underlying types. 10% Mark Shannon, Dong-hee Na, Brandt Bucher, Dennis Sweeney
下标读取 a[i] Subscripting container types such aslist,tupleanddictdirectly index the underlying data structures.Subscripting custom__getitem__is also inlined similar toInlined Python function calls. 10-25% Irit Katriel, Mark Shannon
下标写入 a[i]=z Similar to subscripting specialization above. 10-25% Dennis Sweeney
函数调用 f(arg)C(arg) Calls to common builtin (C) functions and types such aslenandstrdirectly call their underlying C version. This avoids going through the internal calling convention. 20% Mark Shannon, Ken Jin
加载全局变量 printlen The object’s index in the globals/builtins namespace is cached. Loading globals and builtins require zero namespace lookups. 1 Mark Shannon
加载特点 o.attr Similar to loading global variables. The attribute’s index inside the class/object’s namespace is cached. In most cases, attribute loading will require zero namespace lookups. 2 Mark Shannon
加载办法调用 o.meth() The actual address of the method is cached. Method loading now has no namespace lookups – even for classes with long inheritance chains. 10-20% Ken Jin, Mark Shannon
特点赋值 o.attr=z Similar to load attribute optimization. 2% in pyperformance Mark Shannon
序列拆包 *seq Specialized for common containers such aslistandtuple. Avoids internal calling convention. 8% Brandt Bucher
  • 1:相似的优化已在 Python 3.8 呈现, 3.11 能针对更多特定状况、减少开支.
  • 2:相似的优化已在 Python 3.8 呈现, 3.11 能针对更多特定状况。 此外,所有对特点的加载都应由bpo-45947 得以加快.

秉持着试试的思想,我自己也写了段简略的代码进行比较,三种不同的操作各跑 5 组,每组多次,最终均匀再输出。代码如下:

"""author: FunnySaltyFish,可在 Github 和  搜此名找到我"""
from timeit import repeat
def sum_test(n):
    s = 0
    for i in range(n):
        s += i
    return s
def mean(arr):
    return sum(arr) / len(arr)
# 看一个列表生成器的履行时间,履行10000次:
t1 = repeat('[i for i in range(100) if i%2==0]', number=10000)
print(f"列表生成器 均匀耗时 {mean(t1):>8.3f}s")
# 履行函数的时间,履行10000次:
t2 = repeat('sum_test(100)', number=10000, globals=globals())
print(f"函数 均匀耗时 {mean(t2):>14.3f}s")
# 履行字典生成器的时间,履行10000次:
t3 = repeat('{i:i for i in range(100) if i%2==0}', number=10000)
print(f"字典生成器 均匀耗时 {mean(t3):>8.3f}s")

Python 3.10.4 运行输出如下:

列表生成器 均匀耗时    3.470s
函数 均匀耗时         2.704s
字典生成器 均匀耗时    4.844s

Python 3.11.0 结果如下:

列表生成器 均匀耗时    4.367s
函数 均匀耗时         2.545s
字典生成器 均匀耗时    4.969s

结果令我很意外,除了函数调用那组,py 311 在其他两项上反而慢于 310。即便我从头跑了几回作用都差不多。可能是我打开的姿态不对?假如您读到这里发现了问题或许想自己试试,也欢迎在评论区一同讨论。

更友好的过错提示

PEP 657 – Include Fine Grained Error Locations in Tracebacks

Py 310 也有这方面的优化,311 则更进一步。

动机

比方说下面的代码:

x['a']['b']['c']['d'] = 1

假如上面一长串中,有任何一个是None,报错便是这个姿态:

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    x['a']['b']['c']['d'] = 1
TypeError: 'NoneType' object is not subscriptable

从这个报错很难看出究竟哪里是None,通常得 debug 或许 print 才知道。

所以在 311 里,报错进化成了这样:

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    x['a']['b']['c']['d'] = 1
    ~~~~~~~~~~~^^^^^
TypeError: 'NoneType' object is not subscriptable

这样就能直观地知道,['c']这里出问题了。
更多比方如下所示:

Traceback (most recent call last):
  File "test.py", line 14, in <module>
    lel3(x)
    ^^^^^^^
  File "test.py", line 12, in lel3
    return lel2(x) / 23
           ^^^^^^^
  File "test.py", line 9, in lel2
    return 25 + lel(x) + lel(x)
                ^^^^^^
  File "test.py", line 6, in lel
    return 1 + foo(a,b,c=x['z']['x']['y']['z']['y'], d=e)
                         ~~~~~~~~~~~~~~~~^^^^^
TypeError: 'NoneType' object is not subscriptable
def foo(x):
    1 + 1/0 + 2
def bar(x):
    try:
        1 + foo(x) + foo(x)
    except Exception:
        raise
bar(bar(bar(2)))
...
Traceback (most recent call last):
  File "test.py", line 10, in <module>
    bar(bar(bar(2)))
            ^^^^^^
  File "test.py", line 6, in bar
    1 + foo(x) + foo(x)
        ^^^^^^
  File "test.py", line 2, in foo
    1 + 1/0 + 2
        ~^~
ZeroDivisionError: division by zero

反常组与 except *

PEP 654 – Exception Groups and except* | peps.python.org

动机

  • 并发过错:很多异步使命的各种框架允许用户一起进行多个使命,并将结果聚合后一同回来,这时进行反常处理就比较费事
  • 在杂乱核算中抛出的多个过错

pep 中列出了几种状况,总的来说很多都环绕“一起有多个过错发生,且每一个都需求处理”的状况。为了更好处理这个问题, 311 提出了反常组

ExceptionGroup

此 pep 提出了两个新的类:BaseExceptionGroup(BaseException)ExceptionGroup(BaseExceptionGroup,Exception). 它们能够被赋给Exception.__cause__Exception.__context__,并且能够通过raiseExceptionGroup(...)+try:...exceptExceptionGroup:...raiseBaseExceptionGroup(...)+try:...exceptBaseExceptionGroup:... 抛出和捕获。
二者的结构参数均接受俩参数:

  • message:消息
  • exceptions:过错列表
    如:ExceptionGroup('issues',[ValueError('badvalue'),TypeError('badtype')])

区别在于,ExceptionGroup只能包裹Exception的子类 ,而BaseExceptionGroup能包裹恣意BaseException的子类。 为行文便利,后文里的“反常组”将代指二者之一。

由于反常组能够嵌套,所以它能形成一个树形结构。办法BaseExceptionGroup.subgroup(condition)能帮咱们获取到满足条件的的子反常组,它的整体结构和原始反常组相同。

>>> eg = ExceptionGroup(
...     "one",
...     [
...         TypeError(1),
...         ExceptionGroup(
...             "two",
...              [TypeError(2), ValueError(3)]
...         ),
...         ExceptionGroup(
...              "three",
...               [OSError(4)]
...         )
...     ]
... )
>>> import traceback
>>> traceback.print_exception(eg)
  | ExceptionGroup: one (3 sub-exceptions)
  +-+---------------- 1 ----------------
    | TypeError: 1
    +---------------- 2 ----------------
    | ExceptionGroup: two (2 sub-exceptions)
    +-+---------------- 1 ----------------
      | TypeError: 2
      +---------------- 2 ----------------
      | ValueError: 3
      +------------------------------------
    +---------------- 3 ----------------
    | ExceptionGroup: three (1 sub-exception)
    +-+---------------- 1 ----------------
      | OSError: 4
      +------------------------------------
>>> type_errors = eg.subgroup(lambda e: isinstance(e, TypeError))
>>> traceback.print_exception(type_errors)
  | ExceptionGroup: one (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | TypeError: 1
    +---------------- 2 ----------------
    | ExceptionGroup: two (1 sub-exception)
    +-+---------------- 1 ----------------
      | TypeError: 2
      +------------------------------------
>>>

假如 subgroup啥也没匹配到,它会回来None;假如你想把匹配的和没匹配的分隔,则能够用spilt办法

>>> type_errors, other_errors = eg.split(lambda e: isinstance(e, TypeError))
>>> traceback.print_exception(type_errors)
  | ExceptionGroup: one (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | TypeError: 1
    +---------------- 2 ----------------
    | ExceptionGroup: two (1 sub-exception)
    +-+---------------- 1 ----------------
      | TypeError: 2
      +------------------------------------
>>> traceback.print_exception(other_errors)
  | ExceptionGroup: one (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | ExceptionGroup: two (1 sub-exception)
    +-+---------------- 1 ----------------
      | ValueError: 3
      +------------------------------------
    +---------------- 2 ----------------
    | ExceptionGroup: three (1 sub-exception)
    +-+---------------- 1 ----------------
      | OSError: 4
      +------------------------------------
>>>

exept *

为了更好处理反常组,新的语法except*就诞生了。它能够匹配反常组的一个或多个反常,并且将其他未匹配的持续传递给其他except*。示例代码如下:

try:
    ...
except* SpamError:
    ...
except* FooError as e:
    ...
except* (BarError, BazError) as e:
    ...

关于上面的比方,假定产生反常的代码为ubhandled=ExceptionGroup('msg',[FooError(1),FooError(2),BazError()])。则except*便是不断对 unhandled进行 spilt,并将未匹配到的结果传下去。对上面的比方:

  1. unhandled.split(SpamError)回来(None,unhandled)unhandled 不变
  2. unhandled.split(FooError) 回来match=ExceptionGroup('msg',[FooError(1),FooError(2)])rest=ExceptionGroup('msg',[BazError()]). 这个 except*块会被履行, esys.exc_info()的值被设为 matchunhandled=rest
  3. 第三个也匹配到了,esys.exc_info()被设为ExceptionGroup('msg',[BazError()])

关于嵌套的状况,示例如下:

>>> try:
...     raise ExceptionGroup(
...         "eg",
...         [
...             ValueError('a'),
...             TypeError('b'),
...             ExceptionGroup(
...                 "nested",
...                 [TypeError('c'), KeyError('d')])
...         ]
...     )
... except* TypeError as e1:
...     print(f'e1 = {e1!r}')
... except* Exception as e2:
...     print(f'e2 = {e2!r}')
...
e1 = ExceptionGroup('eg', [TypeError('b'), ExceptionGroup('nested', [TypeError('c')])])
e2 = ExceptionGroup('eg', [ValueError('a'), ExceptionGroup('nested', [KeyError('d')])])
>>>

更多杂乱的状况请参阅 pep 内容,此处不再赘述

TOML 的标准库支撑

相似于 Jsonyaml,TOML 也是一种配置文件的格局。它于 2021.1 发布了 v1.0.0 版别。现在,不少的 python 包就运用 TOML 书写配置文件。

TOML 旨在成为一个语义显着且易于阅览的最小化配置文件格局。
TOML 被设计成能够无歧义地映射为哈希表
TOML 应该能很容易地被解析成各种言语中的数据结构。

一个 TOML 的文件比方如下:

name = "FunnySaltyFish"
author.juejin = "https:///user/2673613109214333" 
author.github = "https://github.com/FunnySaltyFish/" 
[blogs]
python1 = "https:///post/7146579580176842783"
python2 = "https:///post/7015590447745613854"

它转化为 Json 如下所示

{
    "name":"FunnySaltyFish",
    "author":{
        "juejin":"https:///user/2673613109214333",
        "github":"https://github.com/FunnySaltyFish/"
    },
    "blogs":{
        "python1":"https:///post/7146579580176842783",
        "python2":"https:///post/7015590447745613854"
    }
}

3.11 中运用的办法也很简略,根本和 json 模块用法相似。不过并未支撑写入,需求的话能够用第三方库 toml

import tomllib
path = "./test.toml"
with open(path, "r", encoding="utf-8") as f:
    s = f.read()
    # loads 从字符串解析
    data = tomllib.loads(s)
    print(data)
# 或许 load 直接读文件
with open(path, "rb") as f2:
    data = tomllib.load(f2)
    print(data)
# tomllib 不支撑写入,能够运用 toml 库,它包括 dump/dumps
# toml.dump 和 toml.dumps 均相似 json.dump 和 json.dumps,不赘述
# with open("./new_config.toml", "w+", encoding="utf-8") as f:
#     toml.dump(data, f)

typing.Self

PEP 673 – Self Type | peps.python.org

这是 python 的 type hint 的增强,假如不太了解,能够参阅我写的 写出更现代化的Python代码:聊聊 Type Hint,里面现已说到了这个特性。
简而言之,typing.Self 用于在没有界说彻底的类中代指自己,简略处理了先有鸡仍是先有蛋的问题。用法如下:

from typing import Self
class Shape:
    def set_scale(self, scale: float) -> Self:
        self.scale = scale
        return self
class Circle(Shape):
    def set_radius(self, radius: float) -> Self:
        self.radius = radius
        return self

Variadic Generics 不定长泛型

要解说这个,咱们需求先提一下泛型。假如你学过其他言语,比方 Java,你不会对这个概念陌生。
举个栗子,假如你期望写一个 f 函数,你期望这个函数支撑传入类型为 list[int]list[str]的参数,也便是各项类型相同且要么为int要么为str的列表。你能够这样写:

from typing import TypeVar
T = TypeVar("T", int, str)
def f(arr: list[T]) -> T:
    pass
i = f([1, 2, 3])  # 合法,i 的类型为 int
s = f(["a", "b", "c"])  # 合法,s 的类型为 str
b = f([1, 2, 3.0]) # 不合法,由于 3.0 不是 int 或 str

T 就代表泛型,根据传入的参数状况,被认定为 intstr
假如需求写泛型类,咱们能够用到 Generic。比方下面的代码:

K = TypeVar("K", int, str)
V = TypeVar("V")
class Item(Generic[K, V]):  
    # Item是一个泛型类,能够确认其中的2个类型
    key: K
    value: V
    def __init__(self, k: K, v: V):
        self.key = k
        self.value = v
i = Item(1, 'a')  # OK Item是泛型类,所以符合要求的类型值都能够作为参数
i2 = Item[int, str](1, 'a')  #  OK 清晰的指定了Item的K, V的类型
i3 = Item[int, int](1, 2)  #  OK 清晰的指定成了另外的类型
i4 = Item[int, int](1, 'a')  # Rejected 由于传入的参数和指定的类型V不同

此代码引自 Python 3.11新加入的和类型体系相关的新特性,一篇很好的文章,咱们也能够去看一下

回到本文,为什么 3.11 又提出了个 TypeVarTuple 呢?让咱们考虑这样的状况:

咱们给定一个函数,它接收一个数组,并且对数据的形状有严格要求。
事实上,假如你常常用 numpytensorflow 或许 pytorch 之类的库,你会常常碰见相似的状况。

def to_gray(videos: Array): ...

但从这个标记中,很难看出数组应该是什么 shape 的。可能是:

batch time height width channels

也或许是

time batch channels height width.

所以,TypeVarTuple就诞生了。咱们能够相似这样去写这个类:

from typing import TypeVar, TypeVarTuple
DType = TypeVar('DType')
Shape = TypeVarTuple('Shape')
class Array(Generic[DType, *Shape]):
    def __abs__(self) -> Array[DType, *Shape]: ...
    def __add__(self, other: Array[DType, *Shape]) -> Array[DType, *Shape]: ...

在运用时就能够限制维度和格局:

from typing import NewType
Height = NewType('Height', int)
Width = NewType('Width', int)
x: Array[float, Height, Width] = Array()

或许甚至指定确切的大小(运用 字面量 Literal)

from typing import Literal as L
x: Array[float, L[480], L[640]] = Array()

更多的介绍请参阅 pep

恣意字符串字面量

PEP 675– Arbitrary Literal String Type

可选的 TypedDict 键

PEP 655– Marking individual TypedDict items as required or potentially-missing

数据类变换

PEP 681– Data Class Transforms

关于上面三者的介绍,我感觉 Python 3.11新加入的和类型体系相关的新特性 现已很清晰了,我就不重复造轮了。感兴趣的读者能够前往阅览

除此之外

除了上面说到的 pep,3.11 的首要更新列表还包括两个 gh,分别为

  • gh-90908– 为 asyncio 引入使命组
  • gh-34627– 正则表达式项支撑 Atomic Grouping ((?>...)) 和 Possessive Quantifiers (*+, ++, ?+, {m,n}+)

感兴趣的同学能够自行前往对应链接阅览。

本文完。

作者 FunnySaltyFish,juejin/Github 可直接搜到。本文仅发于和个人网站(blog.funnysaltyfish.fun),如需交流建议前往这俩平台找我(优先)