Python 3.10 于 2021-10-04 发布,至今已大半年,目前 AWS 的 Lambda 尚未直接支持,但用 Docker 镜像的方式使用 AWS Lambda 是可以使用 Python 3.10。Python 一年一发布的节奏比 Java LTS 还紧密。下一个版本 Python 3.11 预计在 2022-10-03 发布。在学习 Python 3.10 之前先回顾一下 Python 3.7, 3.8, 3.9 的特性(不想关心之前版本的变迁可直接跳跃到下方的 Python 3.10 新特性去)
- breakpoint()
- 数据类(@dataclass)
- 类型提示强化和延迟注解求值
- 时间精度的提高
- 保证字典的顺序
- async 和 await 成为关键字
- asyncio.run() 简化事件循环
- 上下文变量(ContextVar) - 可实现 ThreadLocal 和 SLF4J 的 MDC 功能
- 赋值表达式(:=) -- Walrus Operator
- 可限定只按位置传递参数
- 更精确的类型提示(增强了 Type Hints)
- 用 f-strings 进行简单的调试(f"{name = }" 输出 name = 'Eric'
- 新模块 importlib.metadata
- Math 和 Statistics 函数
- 更聪明的警告信息
- 字典的并集操作( d1 | d2)
- 删除字符串前缀和后缀的函数(removeprefix(), removesuffix())
- 更简单的泛型提示(list[str], 而不用 typing.List[str])
- DateTime 新增时区支持(不再依赖 pytz)
- shutdown() Executor 时可取消待处理任务
- random.Random.randbytes() 生成随机字节
- typing.Annotated 类型提示(distance: Annotated[float, 'feet'] 类型加注解提示)
回顾完上面后,开始学习 Python 3.10 的新特性, 参考 What's New In Python 3.10
带圆括号的上下文管理器
使用 Python 3.9.12 的 with
就可以一次管理个资源,如
1 2 3 4 5 6 7 |
with (open('test.txt') as f1, # 外加圆括号后其中语句可分多行写 open('test.txt') as f2): ... # 或者 with open('test.txt') as f1, open('test.txt') as f2): # 只能写在同一行 ... |
不知道为什么把它作为了 Python 3.10 的新特性,而且经测试 Python 3.9.0 就能用圆括号框住多个资源,Python 3.8 不支持 with 圆括号的语法
更友好的错误提示
像 [1,
, {a:2, 2:
会提示 '[' was never closed
和 '{" was never closed
, 而不是笼统的说 invalid syntax
。
还有更多的更聪明的错误提示,没必要一一列出,反正也不影响如何书写代码,只是看到了错误信息容易定位错误,多用,谁用谁知道
结构化模式匹配
Python 没有 switch 语句,所以不得不用 if...elif..else
语句,不过现在 Python 3.10 从 Scala 学来了更高级的 match...case
语句。模式匹配功能很强大,展开来值得单独写一篇
匹配字面值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def http_error(status): match status: case 400: print("Bad request") case 404: return "Not found" case 401 | 402 | 403: return "Not allowed" case _: return "Unknown" print(http_error(400)) print('--') print(http_error(404)) print(http_error(401)) print(http_error(405)) |
执行后输出为
Bad request
None
--
Not found
Not allowed
Unknown
Python 的 match...case
是语句,而非表达式,所以它本身不能赋值给变量的。
每个 case 后相当于自动加上了 break
语句, case _:
相当于 case default:
, case 后的多个匹配可用 |
连接。
没有 case _:
的 match 相当于什么也没匹配到,是一个 no-op 操作行为,如果以上函数中拿掉 case _:
, http_error(405)
得到的就是 None
更强大的匹配功能还在后头,比如对类型,类型值的匹配,首先看它如何匹配一个元组,并获取其中的值
1 2 3 4 5 6 7 8 9 10 11 12 |
def test(point): match point: case (0, 0): print("Origin") case (0, y): print(f"{y = }") case (): print("Is a tuple") test(()) test((0,3)) test((0,0)) |
执行后输出为
Is a tuple
y = 3
Origin
进一步延伸,可以有更丰富的匹配功能
- case []: 是一个列表
- case [1, 3, a]: 匹配前两个值为 1, 3 的三元素列表,第三个元素赋给 a 变量
- case [1,2, *a]: 结合 Python 的集合拆解功能, 这里匹配至少两元素的列表,头两元素为 1, 2, 其余元素赋给 a 列表
- case Point(x=0, y=0): 假如声明了类
class Point: x: int; y: int
, 它则可匹配到 x=0, y=0 的 Point 对象, - case Point(x=xvar, y=yvar): 匹配 Point 类型,并捕获 x, y 的值,用
xvar
和yvar
引用 x, y 的值 - case Point(): 匹配 Point 类型,相当于
isinstance(p, Point)
- case [Point(0,0)]: 嵌套匹配 Point(0,0) 单元素列表
如果用 case Point(x,y), 则会报错
case Point(x, y):
TypeError: Point() accepts 0 positional sub-patterns (2 given)
因为在 Point 中 x, y 是没有顺序的, 相当于是按名赋值的,只要给 Point 声明一个 __match_args__
则可用 Point(x, y)
进行匹配,完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Point: __match_args__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = y def test(point): match point: case Point(x, y): print(f'{x = }, {y = }') p = Point(1, 2) test(p) |
输出为
x = 1, y = 2
- case Point(x, y) if x == y: 加约束条件的匹配
- case {"name": b, "id": 1}: 匹配字典
- case {"name": b, **rest}: 匹配字典剩余的项
- case (Point(x1, y1, Point(x2, y2) as p2): 用
as
关键字捕获子模式 - 匹配枚举值
1 2 3 4 5 6 7 8 |
from enum import Enum class Color(Enum): RED = 0 GREEN = 1 match color: case Color.RED: ... |
EncodingWarning 提示
增加 -X warn_default_encoding 选项和 PYTHONWARNDEFAULTENCODING 来启用相应警告,用于定位由于打开文件编码引起的问题
新的联合类型提示
Python 3.10 前的联合类型提示用 typing.Union
number: Union[int, float]
现在直接用 |
number: int | float
在进行实例类型判断时也可以用 |
, 如
isinstance(1, int | str)
类型别名
Python 3.10 之前类型别名和使用是这样写
1 2 |
StrCache = 'Cache[str]' cache: StrCache = {} |
这样让 StrCache
看起就像是一个值为 Cache[str]
的字符串变量,也确实是,用 type(StrCache)
得到的就是 <class 'str'>
.
Python 3.10 的写法让它看起来更像是一个类型
1 2 3 4 |
from typing import TypeAlias StrCache: TypeAlias = 'Cache[str]' cache: StrCache = {} |
因为他们是 type hints 的范畴,所以本质上都是
1 2 3 |
cache: 'Cache[str]' = {} # Python 解释器看到的其实只是 cache = {} |
类型别名用作内置类型提示
1 2 |
Card: TypeAlias = tuple[str, str] card: Card = ("J", "Heart") |
更严格的 zip() 函数
zip() 函数作用于两个序列,生成 tuple 序列,Python 不要求两个序列等长,只以短序列为准,长序列多出部分被忽略
1 2 3 4 5 6 |
>>> list(zip([1], [2,3])) [(1, 2)] >>> list(zip(['a', 'b'], [1])) [('a', 1)] >>> list(zip(['a'], [1, 2])) [('a', 1)] |
这会造成两个序列都比较长的时候,得到非期望的结果,多数时候我们应用 zip()
函数想要完全配对。所以在 Python 3.10 中加入了 strict
参数
1 2 3 4 |
>>> list(zip(['a'], [1, 2], strict=True)) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: zip() argument 2 is longer than argument 1 |
最后总结,Python 3.10 的一个关键特性就是结构化模式匹配。
链接:
本文链接 https://yanbin.blog/python-3-10-new-features/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。