我正在参与「启航方案」
去年(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 , andstr take custom fast paths for their underlying types. |
10% | Mark Shannon, Dong-hee Na, Brandt Bucher, Dennis Sweeney |
下标读取 | a[i] |
Subscripting container types such aslist ,tuple anddict directly 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 aslen andstr directly call their underlying C version. This avoids going through the internal calling convention. |
20% | Mark Shannon, Ken Jin |
加载全局变量 |
print len
|
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 aslist andtuple . 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
,并将未匹配到的结果传下去。对上面的比方:
-
unhandled.split(SpamError)
回来(None,unhandled)
,unhandled
不变 -
unhandled.split(FooError)
回来match=ExceptionGroup('msg',[FooError(1),FooError(2)])
、rest=ExceptionGroup('msg',[BazError()])
. 这个except*
块会被履行,e
和sys.exc_info()
的值被设为match
,unhandled
=rest
- 第三个也匹配到了,
e
和sys.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 的标准库支撑
相似于 Json
和 yaml
,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
就代表泛型,根据传入的参数状况,被认定为 int
或 str
。
假如需求写泛型类,咱们能够用到 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
呢?让咱们考虑这样的状况:
咱们给定一个函数,它接收一个数组,并且对数据的形状有严格要求。
事实上,假如你常常用 numpy
、tensorflow
或许 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),如需交流建议前往这俩平台找我(优先)