Python 3.10.0 稳定版本于2021年10月5日正式发布了。这个版本包含了很多新特性和优化,这些新特性很可能会改变未来的 Python 生态系统,使其朝着更明确,更易读的方向发展,同时保持我们熟知和喜欢的易用性。:
Major new features of the 3.10 series, compared to 3.9
Among the new major new features and changes so far:
重要的弃用、移除或限制:
PEP 644,要求 OpenSSL 1.1.1 或更新的版本
PEP 632,弃用 distutils 模块。
PEP 623,弃用并准备移除 PyUnicodeObject 中的 wstr 成员。
PEP 624,移除 Py_UNICODE 编码器 API
PEP 597,增加可选的 EncodingWarning
整个 distutils 包已被废弃,将在 Python 3.12 中移除。其用指定包构建程序的功能已被第三方软件包 setuptools 和 packaging 完全取代,而且大多数其他常用的 API 在标准库的其他地方都可以调用(如 platform 、 shutil 、 subprocess 或 sysconfig)。目前没有迁移 distutils 其他功能的计划,用到其他功能的应用程序应该准备好自己保留一份拷贝。
在 Python 3.8 中废弃的 bdist_wininst 命令已被移除。现在在 Windows 中发布二进制包推荐采用 bdist_wheel 命令。
hashlib 模块要求 OpenSSL 1.1.1 或更新版本。 hashlib 模块已初步支持 OpenSSL 3.0.0。
新的语法特性:
PEP 634, 结构化模式匹配: 规范说明
PEP 635, 结构化模式匹配: 动机与理由
PEP 636, 结构化模式匹配: 教程
bpo-12782,加圆括号的上下文管理器现在正式被允许使用。
带括号的上下文管理器
现在已支持使用外层圆括号来使多个上下文管理器可以连续多行地书写。 这允许将过长的上下文管理器集能够以与之前 import 语句类似的方式格式化为多行的形式。 例如,以下这些示例写法现在都是有效的:
with (open("temp.txt",encoding='utf-8') as foo,
open("score.csv",encoding='utf-8') as bar):
print(foo.read())
print(bar.read())
with (CtxManager() as example):
...
with (
CtxManager1(),
CtxManager2()
):
...
with (CtxManager1() as example,
CtxManager2()):
...
with (CtxManager1(),
CtxManager2() as example):
...
with (
CtxManager1() as example1,
CtxManager2() as example2
):
...
在被包含的分组末尾过可以使用一个逗号作为结束:
with (
CtxManager1() as example1,
CtxManager2() as example2,
CtxManager3() as example3,
):
...
新的类型标注特性:
PEP 604,允许 X | Y 形式的联合类型写法
PEP 613,显式类型别名
PEP 612,形参规格变量
新增类型注释联合操作符
PEP 604: 新的类型联合运算符
引入了启用 X | Y 语法的类型联合运算符。 这提供了一种表示 ‘类型 X 或类型 Y’ 的相比使用 typing.Union 更清晰的方式,特别是在类型提示中。
在之前的 Python 版本中,要为可接受多种类型参数的函数应用类型提示,使用的是 typing.Union例如,我们有一个预期为 int 或 float 的变量,可以写为 int | float ,如下所示:
def square(number: Union[int, float]) -> Union[int, float]:
return number ** 2
def square(number: int | float) -> int | float:
return number ** 2
PEP 612 — Parameter Specification Variables
PEP 626 — Precise line numbers for debugging and other tools.
解释器的改进:
PEP 626:在调试和其他工具中使用精确的行号
PEP 626 带来了更精确可靠的行号用于调试、性能分析和测试工具。 所有被执行的代码行都会并且只有被执行的代码行才会生成带有正确行号的追踪事件。
帧对象的 f_lineno 属性将总是包含预期的行号。
代码对象的 co_lnotab 属性已被弃用并将在 3.12 中被移除。 需要从偏移量转换为行号的代码应改用新的 co_lines() 方法。
标准库中的新特性:
PEP 618,向 zip 添加可选的长度检查,
函数 zip() 增加 strict 参数。
函数 zip() 增加 strict 参数,如果设置 strict = True,而传输的参数的长度不相等将会抛出异常,新的 strict 参数不是盲目地截断不匹配的数据,而是使我们能够控制它的行为,这将使很多开发人员免于遭受麻烦。如下代码所示:
x = range(5)
y = range(6)
print(list(zip(x, y)))
# [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
print(list(zip(x, y, strict=True)))
# ValueError: zip() argument 2 is longer than argument 1
PEP 613: 类型别名
PEP 484 引入了类型别名的概念,只要求它们是不带标注的最高层级赋值。 这种简单性有时会使得类型检查器难以区分类型别名和普通赋值,特别是当涉及到前向引用或无效类型的时候。 例如在比较:
StrCache = 'Cache[str]' # a type alias
LOG_PREFIX = 'LOG[DEBUG]' # a module constant
现在 typing 模块具有一个特殊值 TypeAlias 可让你更明确地声明类型别名:
StrCache: TypeAlias = 'Cache[str]' # a type alias
LOG_PREFIX = 'LOG[DEBUG]' # a module constant
PEP 634:结构化模式匹配
增加了采用模式加上相应动作的 match 语句 和 case 语句 的形式的结构化模式匹配。 模式由序列、映射、基本数据类型以及类实例构成。 模式匹配使得程序能够从复杂的数据类型中提取信息、根据数据结构实现分支,并基于不同的数据形式应用特定的动作。
语法与操作
模式匹配的通用语法如下:
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
match 语句接受一个表达式并将其值与以一个或多个 case 语句块形式给出的一系列模式进行比较。 具体来说,模式匹配的操作如下:
使用具有特定类型和形状的数据 (subject)
针对 subject 在 match 语句中求值
从上到下对 subject 与 case 语句中的每个模式进行比较直到确认匹配到一个模式。
执行与被确认匹配的模式相关联的动作。
如果没有确认到一个完全的匹配,则如果提供了使用通配符 _ 的最后一个 case 语句,则它将被用作已匹配模式。 如果没有确认到一个完全的匹配并且不存在使用通配符的 case 语句,则整个 match 代码块不执行任何操作。
声明性方式
读者可能是通过 C, Java 或 JavaScript (以及其他许多语言) 中的 switch 语句将一个目标 (数据对象) 与一个字面值 (模式) 进行匹配的简单例子了解到模式匹配的概念的。 switch 语句常常被用来将一个对象/表达式与包含在 case 语句中的字面值进行比较。
更强大的模式匹配例子可以在 Scala 和 Elixir 等语言中找到。 这种结构化模式匹配方式是“声明性”的并且会显式地为所要匹配的数据指定条件(模式)。
虽然使用嵌套的“if”语句的“命令性”系列指令可以被用来完成类似结构化模式匹配的效果,但它没有“声明性”方式那样清晰。 相反地,“声明性”方式指定了一个匹配所要满足的条件,并且通过其显式的模式使之更为易读。 虽然结构化模式匹配可以采取将一个变量与一个 case 语句中的字面值进行比较的最简单形式来使用,但它对于 Python 的真正价值在于其针对目标类型和形状的处理操作。
简单模式:匹配一个字面值
让我们把这个例子看作是模式匹配的最简单形式:一个值,即主词,被匹配到几个字面值,即模式。在下面的例子中,status 是匹配语句的主词。模式是每个 case 语句,字面值代表请求状态代码。匹配后,将执行与该 case 相关的动作:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
如果传给上述函数的 status 为 418,则会返回 “I’m a teapot”。 如果传给上述函数的 status 为 500,则带有 的 case 语句将作为通配符匹配,并会返回 “Something’s wrong with the internet”。 请注意最后一个代码块:变量名 将作为 通配符 并确保目标将总是被匹配。 _ 的使用是可选的。
你可以使用 | (“ or ”)在一个模式中组合几个字面值:
case 401 | 403 | 404:
return "Not allowed"
无通配符的行为
如果我们修改上面的例子,去掉最后一个 case 块,这个例子就变成:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
如果不在 case 语句中使用 _,可能会出现不存在匹配的情况。如果不存在匹配,则行为是一个 no-op。例如,如果传入了值为 500 的 status ,就会发生 no-op。
带有字面值和变量的模式
模式可以看起来像解包形式,而且模式可以用来绑定变量。在这个例子中,一个数据点可以被解包为它的 x 坐标和 y 坐标:
# point is an (x, y) tuple
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
第一个模式有两个字面值 (0, 0) ,可以看作是上面所示字面值模式的扩展。接下来的两个模式结合了一个字面值和一个变量,而变量 绑定 了一个来自主词的值(point)。 第四种模式捕获了两个值,这使得它在概念上类似于解包赋值 (x, y) = point 。
模式和类
如果你使用类来结构化你的数据,你可以使用类的名字,后面跟一个类似构造函数的参数列表,作为一种模式。这种模式可以将类的属性捕捉到变量中:
class Point:
x: int
y: int
def location(point):
match point:
case Point(x=0, y=0):
print("Origin is the point's location.")
case Point(x=0, y=y):
print(f"Y={y} and the point is on the y-axis.")
case Point(x=x, y=0):
print(f"X={x} and the point is on the x-axis.")
case Point():
print("The point is located somewhere else on the plane.")
case _:
print("Not a point")
带有位置参数的模式
你可以在某些为其属性提供了排序的内置类(例如 dataclass)中使用位置参数。 你也可以通过在你的类中设置 match_args 特殊属性来为模式中的属性定义一个专门的位置。 如果它被设为 (“x”, “y”),则以下模式均为等价的(并且都是将 y 属性绑定到 var 变量):
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
嵌套模式
模式可以任意地嵌套。 例如,如果我们的数据是由点组成的短列表,则它可以这样被匹配:
match points:
case []:
print("No points in the list.")
case [Point(0, 0)]:
print("The origin is the only point in the list.")
case [Point(x, y)]:
print(f"A single point {x}, {y} is in the list.")
case [Point(0, y1), Point(0, y2)]:
print(f"Two points on the Y axis at {y1}, {y2} are in the list.")
case _:
print("Something else is found in the list.")
复杂模式和通配符
到目前为止,这些例子仅在最后一个 case 语句中使用了 。 但通配符可以被用在更复杂的模式中,例如 (‘error’, code, )。 举例来说:
match test_variable:
case ('warning', code, 40):
print("A warning has been received.")
case ('error', code, _):
print(f"An error {code} occurred.")
在上述情况下,test_variable 将可匹配 (‘error’, code, 100) 和 (‘error’, code, 800)。
守护项
我们可以向一个模式添加 if 子句,称为“守护项”。 如果守护项为假值,则 match 将继续尝试下一个 case 语句块。 请注意值的捕获发生在守护项被求值之前。:
match point:
case Point(x, y) if x == y:
print(f"The point is located on the diagonal Y=X at {x}.")
case Point(x, y):
print(f"Point is not on the diagonal.")
其他关键特性
一些其他关键特性:
类似于解包赋值,元组和列表模式具有完全相同的含义,而且实际上能匹配任意序列。 从技术上说,目标必须为一个序列。 因而,一个重要的例外是模式不能匹配迭代器。 而且,为了避免一个常见的错误,序列模式不能匹配字符串。
序列模式支持通配符: [x, y, rest] 和 (x, y, rest) 的作用类似于解包赋值中的通配符。 在 之后的名称也可以为 _,因此 (x, y, ) 可以匹配包含两个条目的序列而不必绑定其余的条目。
映射模式: {“bandwidth”: b, “latency”: l} 会从一个字典中捕获 “bandwidth” 和 “latency” 值。 与序列模式不同,额外的键会被忽略。 也支持通配符 rest。 (但 是冗余的,因而不被允许。)
子模式可使用 as 关键字来捕获:
case (Point(x1, y1), Point(x2, y2) as p2): ...
x1, y1, x2, y2 等绑定就如你在没有 as 子句的情况下所期望的,而 p2 会绑定目标的整个第二项。
大多数字面值是按相等性比较的。 但是,单例对象 True, False 和 None 则是按标识号比较的。
命名常量也可以在模式中使用。 这些命名常量必须为带点号的名称以防止常量被解读为捕获变量:
from enum import Enum
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues :(")
match command.split():
case ["quit"]:
print("Goodbye!")
quit_game()
case ["look"]:
current_room.describe()
case ["get", obj]:
character.get(obj, current_room)
case ["go", direction]:
current_room = current_room.neighbor(direction)
# The rest of your commands go here
PEP 597 — Add optional EncodingWarning
可选的 EncodingWarning 和 encoding=”locale” 选项
TextIOWrapper 和 open() 的默认编码格式取决于具体的平台和语言区域设置。 由于 UTF-8 被用于大多数 Unix 平台,当打开 UTF-8 文件 (例如 JSON, YAML, TOML, Markdown) 时省略 encoding 选项是一个非常常见的错误。 例如:
# BUG: "rb" mode or encoding="utf-8" should be used.
with open("data.json") as f:
data = json.load(f)
为了便于查找此类错误,增加了可选的 EncodingWarning。 它会在 sys.flags.warn_default_encoding 为真值并使用了语言区域指定的默认编码格式时被发出。
增加了 -X warn_default_encoding 选项和 PYTHONWARNDEFAULTENCODING 来启用相应警告。
新增整数的位计数器 int.bit_count()
此新方法使我们能够计算整数的二进制表示形式中 1 的个数,在某些场景下这个函数非常实用且高效。
ls = [0, 1, 2, 3, 10, 11, 12, 100, 101, 102]
for x in ls:
print(f'{x} = {x.bit_count()}')
结果为整数以二进制位为 1 的个数:
0 = 0 # 00000000
1 = 1 # 00000001
2 = 1 # 00000010
3 = 2 # 00000011
10 = 2 # 00001010
11 = 3 # 00001011
12 = 2 # 00001100
100 = 3 # 01100100
101 = 4 # 01100101
102 = 4 # 01100110
更清楚的错误消息
SyntaxError
现在当解析包含有未关闭括号的代码时解释器会包括未关闭括号的位置而不是显示 SyntaxError: unexpected EOF while parsing 并指向某个不正确的位置。 例如,考虑以下代码(注意未关闭的 “ { ”):
类似地,涉及未关闭字符串字面值 (单重引号和三重引号) 的错误现在会指向字符串的开头而不是报告 EOF/EOL。
3.9:
print ("Hello"
print ("What's going on?")
# print ("What's going on?")
# ^
# SyntaxError: invalid syntax
{x,y for x,y in range(100)}
# {x,y for x,y in range(100)}
# ^
# SyntaxError: invalid syntax
expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4,
38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6,
some_other_code = foo()
# "test.py", line 3
# some_other_code = foo()
# ^
# SyntaxError: invalid syntax
foo(x, z for z in range(10), t, w)
# test.py", line 1
# foo(x, z for z in range(10), t, w)
# ^
# SyntaxError: Generator expression must be parenthesized
3.10:
print ("Hello"
print ("What's going on?")
# print ("Hello"
# ^
# SyntaxError: '(' was never closed
{x,y for x,y in range(100)}
# {x,y for x,y in range(100)}
# ^^^
# SyntaxError: did you forget parentheses around the comprehension target?
expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4,
38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6,
some_other_code = foo()
# "test.py", line 1
# expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4,
# ^
# SyntaxError: '{' was never closed
解释器所引发的 SyntaxError 异常现在将高亮构成语法错误本身的完整异常错误内容,而不是仅提示检测到问题的位置。
foo(x, z for z in range(10), t, w)
# "test.py", line 1
# foo(x, z for z in range(10), t, w)
# ^^^^^^^^^^^^^^^^^^^^
# SyntaxError: Generator expression must be parenthesized
添加了大量新增的专门化 SyntaxError 异常消息。 其中最主要的一些如下所示:
在代码块之前缺失 ::
>>> if rocket.position > event_horizon
File "<stdin>", line 1
if rocket.position > event_horizon
^
SyntaxError: expected ':'
在推导式的目标中有不带圆括号的元组:
>>> {x,y for x,y in zip('abcd', '1234')}
File "<stdin>", line 1
{x,y for x,y in zip('abcd', '1234')}
^
SyntaxError: did you forget parentheses around the comprehension target?
在多项集字面值中和表达式之间缺失逗号:
>>> items = {
... x: 1,
... y: 2
... z: 3,
File "<stdin>", line 3
y: 2
^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
多个异常类型不带圆括号:
>>> try:
... build_dyson_sphere()
... except NotEnoughScienceError, NotEnoughResourcesError:
File "<stdin>", line 3
except NotEnoughScienceError, NotEnoughResourcesError:
^
SyntaxError: multiple exception types must be parenthesized
字典字面值中缺失 : 和值:
>>> values = {
... x: 1,
... y: 2,
... z:
... }
File "<stdin>", line 4
z:
^
SyntaxError: expression expected after dictionary key and ':'
>>> values = {x:1, y:2, z w:3}
File "<stdin>", line 1
values = {x:1, y:2, z w:3}
^
SyntaxError: ':' expected after dictionary key
try 代码块不带 except 或 finally 代码块:
>>> try:
... x = 2
... something = 3
File "<stdin>", line 3
something = 3
^^^^^^^^^
SyntaxError: expected 'except' or 'finally' block
在比较中使用 = 而不是 ==:
>>> if rocket.position = event_horizon:
File "<stdin>", line 1
if rocket.position = event_horizon:
^
SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?
在 f-字符串中使用 *:
>>> f"Black holes {*all_black_holes} and revelations"
File "<stdin>", line 1
(*all_black_holes)
^
SyntaxError: f-string: cannot use starred expression here
IndentationError
许多 IndentationError 异常现在具有更多上下文来提示是何种代码块需要缩进,包括语句的位置:
>>> def foo():
... if lel:
... x = 2
File "<stdin>", line 3
x = 2
^
IndentationError: expected an indented block after 'if' statement in line 2
AttributeError
当打印 AttributeError 时,PyErr_Display() 将提供引发异常的对象中类似属性名称的建议:
>>> collections.namedtoplo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'collections' has no attribute 'namedtoplo'. Did you mean: namedtuple?
NameError
当打印解释器所引发的 NameError 时,PyErr_Display() 将提供引发异常的函数中类似变量名称的建议:
>>> schwarzschild_black_hole = None
>>> schwarschild_black_hole
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'schwarschild_black_hole' is not defined. Did you mean: schwarzschild_black_hole?
现在赋值表达式可以不带圆括号地在集合字面值和集合推导式中使用,也可以在序列索引号中使用(但不能用于切片)。
现在 dict.keys(), dict.values() 和 dict.items() 所返回的视图都有一个 mapping 属性,它给出包装了原始字典的 types.MappingProxyType 对象。
增加了两个新的内置函数 —— aiter() 和 anext() 以分别提供与 iter() 和 next() 对应的异步版本。
float 类型和 decimal.Decimal 类型的 NaN 值的哈希值现在取决于对象身份。以前,即便 NaN 值彼此不等,也都是哈希为 0。在创建包含多个 NaN 的字典和集合时,由于哈希冲突过度,导致了运行代价可能会二次方增长。
array.array 的 index() 方法拥有可选的 start 和 stop 参数。
现在 encodings.normalize_encoding() 会忽略非 ASCII 字符。
IDLE 与 idlelib
IDLE 会发起 sys.excepthook() 调用(不带 ‘-n’ 参数启动时)。之前用户钩子会被忽略。此修改已被反向移植到 3.9 维护发行版中。
增加了 Shell 侧栏。 将主提示符(“>>>”)移至侧栏。二级提示符(“…”)也加入侧栏。在编辑器的行号侧栏上鼠标单击和可选的拖动,会选定一行或多行文本。在选定文本行后右击将显示包含“copy with prompts”的上下文菜单。这会将侧栏的提示符与选定文本行合并。该选项也会在文本的上下文菜单中显示。
不用制表符而用空格来缩进交互式代码。这使得交互式代码“看起来很正确”。增加 shell 侧边栏的一个主要动机就是为了实现这一点。