一、带圆括号的上下文管理器
现在已支持使用外层圆括号来使多个上下文管理器可以连续多行地书写。 这允许将过长的上下文管理器集能够以与之前 import 语句类似的方式格式化为多行的形式。 例如,以下这些示例写法现在都是有效的:
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,
):
...
这个新语法使用了新解析器的非 LL(1) 功能。 请查看 PEP 617 来了解更多细节。
(由 Guido van Rossum, Pablo Galindo 和 Lysandros Nikolaou 在 bpo-12782 和 bpo-40334 中贡献。)
二、更清楚的错误消息
SyntaxError
现在当解析包含有未关闭括号的代码时解释器会包括未关闭括号的位置而不是显示 SyntaxError: unexpected EOF while parsing 并指向某个不正确的位置。 例如,考虑以下代码(注意未关闭的 “ { ”):
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()
之前版本的解释器会报告令人迷惑的语法错误位置:
File "example.py", line 3
some_other_code = foo()
^
SyntaxError: invalid syntax
但在 Python 3.10 中则会发出信息量更多的错误提示:
File "example.py", line 1
expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4,
^
SyntaxError: '{' was never closed
类似地,涉及未关闭字符串字面值 (单重引号和三重引号) 的错误现在会指向字符串的开头而不是报告 EOF/EOL。
这些改进的灵感来自 PyPy 解释器之前所进行的工作。
(由 Pablo Galindo 在 bpo-42864 以及 Batuhan Taskaya 在 bpo-40176 中贡献。)
解释器所引发的 SyntaxError 异常现在将高亮构成语法错误本身的完整异常错误内容,而不是仅提示检测到问题的位置。 这样,不再(同 Python 3.10 之前那样)仅显示:
>>> foo(x, z for z in range(10), t, w)
File "<stdin>", line 1
foo(x, z for z in range(10), t, w)
^
SyntaxError: Generator expression must be parenthesized
现在 Python 3.10 将这样显示异常:
>>>
>>> foo(x, z for z in range(10), t, w)
File "<stdin>", line 1
foo(x, z for z in range(10), t, w)
^^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized
这个改进是由 Pablo Galindo 在 bpo-43914 中贡献的。
大量新增的专门化 SyntaxError 异常消息已被添加。 其中最主要的一些如下所示:
>>>
>>> if rocket.position > event_horizon
File "<stdin>", line 1
if rocket.position > event_horizon
^
SyntaxError: expected ':'
(由 Pablo Galindo 在 bpo-42997 中贡献。)
在推导式的目标中有不带圆括号的元组:
>>>
>>> {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?
(由 Pablo Galindo 在 bpo-43017 中贡献。)
在多项集字面值中和表达式之间缺失逗号:
>>>
>>> items = {
... x: 1,
... y: 2
... z: 3,
File "<stdin>", line 3
y: 2
^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
(由 Pablo Galindo 在 bpo-43822 中贡献。)
多个异常类型不带圆括号:
>>>
>>> try:
... build_dyson_sphere()
... except NotEnoughScienceError, NotEnoughResourcesError:
File "<stdin>", line 3
except NotEnoughScienceError, NotEnoughResourcesError:
^
SyntaxError: multiple exception types must be parenthesized
(由 Pablo Galindo 在 bpo-43149 中贡献。)
字典字面值中缺失 :
和值:
>>>
>>> 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
(由 Pablo Galindo 在 bpo-43823 中贡献)
try
代码块不带 except
或 finally
代码块:
>>>
>>> try:
... x = 2
... something = 3
File "<stdin>", line 3
something = 3
^^^^^^^^^
SyntaxError: expected 'except' or 'finally' block
(由 Pablo Galindo 在 bpo-44305 中贡献。)
在比较中使用 =
而不是 ==
:
>>>
>>> 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 '='?
(由 Pablo Galindo 在 bpo-43797 中贡献。)
在 f-字符串中使用 *
:
>>>
>>> f"Black holes {*all_black_holes} and revelations"
File "<stdin>", line 1
(*all_black_holes)
^
SyntaxError: f-string: cannot use starred expression here
(由 Pablo Galindo 在 bpo-41064 中贡献。)
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?
(由 Pablo Galindo 在 bpo-38530 中贡献。)
警告
请注意如果未调用 PyErr_Display()
来显示错误则此特性将没有效果,这可能发生在使用了某些其他自定义错误显示函数的时候。 这在某些 REPL 例如 IPython 上是一种常见的情况。
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?
(由 Pablo Galindo 在 bpo-38530 中贡献。)
警告
请注意如果未调用 PyErr_Display()
来显示错误则此特性将没有效果,这可能发生在使用了某些其他自定义错误显示函数的时候。 这在某些 REPL 例如 IPython 中是一种常见的情况。
PEP 626:在调试和其他工具中使用精确的行号
PEP 626 带来了更精确可靠的行号用于调试、性能分析和测试工具。 所有被执行的代码行都会并且只有被执行的代码行才会生成带有正确行号的追踪事件。
帧对象的 f_lineno
属性将总是包含预期的行号。
代码对象的 co_lnotab
属性已被弃用并将在 3.12 中被移除。 需要从偏移量转换为行号的代码应改用新的 co_lines()
方法。
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.")