现在已支持使用外层圆括号来使多个上下文管理器可以连续多行地书写。 这允许将过长的上下文管理器集能够以与之前 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,
):
...
现在当解析包含有未关闭括号的代码时解释器会包括未关闭括号的位置而不是显示 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 "", 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 "", 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 "", line 1
if rocket.position > event_horizon
^
SyntaxError: expected ':'
(Contributed by Pablo Galindo in bpo-42997.)
在推导式的目标中有不带圆括号的元组:
>>>
>>> {x,y for x,y in zip('abcd', '1234')}
File "", line 1
{x,y for x,y in zip('abcd', '1234')}
^
SyntaxError: did you forget parentheses around the comprehension target?
(Contributed by Pablo Galindo in bpo-43017.)
在多项集字面值中和表达式之间缺失逗号:
>>>
>>> items = {
... x: 1,
... y: 2
... z: 3,
File "", line 3
y: 2
^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
(Contributed by Pablo Galindo in bpo-43822.)
多个异常类型不带圆括号:
>>>
>>> try:
... build_dyson_sphere()
... except NotEnoughScienceError, NotEnoughResourcesError:
File "", line 3
except NotEnoughScienceError, NotEnoughResourcesError:
^
SyntaxError: multiple exception types must be parenthesized
(Contributed by Pablo Galindo in bpo-43149.)
字典字面值中缺失 : 和值:
>>>
>>> values = {
... x: 1,
... y: 2,
... z:
... }
File "", line 4
z:
^
SyntaxError: expression expected after dictionary key and ':'
>>> values = {x:1, y:2, z w:3}
File "", line 1
values = {x:1, y:2, z w:3}
^
SyntaxError: ':' expected after dictionary key
(Contributed by Pablo Galindo in bpo-43823.)
try 代码块不带 except 或 finally 代码块:
>>>
>>> try:
... x = 2
... something = 3
File "", line 3
something = 3
^^^^^^^^^
SyntaxError: expected 'except' or 'finally' block
(Contributed by Pablo Galindo in bpo-44305.)
在比较中使用 = 而不是 ==:
>>>
>>> if rocket.position = event_horizon:
File "", line 1
if rocket.position = event_horizon:
^
SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?
(Contributed by Pablo Galindo in bpo-43797.)
在 f-字符串中使用 *:
>>>
>>> f"Black holes {*all_black_holes} and revelations"
File "", line 1
(*all_black_holes)
^
SyntaxError: f-string: cannot use starred expression here
(Contributed by Pablo Galindo in bpo-41064.)
许多 IndentationError 异常现在具有更多上下文来提示是何种代码块需要缩进,包括语句的位置:
>>>
>>> def foo():
... if lel:
... x = 2
File "", line 3
x = 2
^
IndentationError: expected an indented block after 'if' statement in line 2
当打印 AttributeError 时,PyErr_Display() 将提供引发异常的对象中类似属性名称的建议:
>>>
>>> collections.namedtoplo
Traceback (most recent call last):
File "", line 1, in
AttributeError: module 'collections' has no attribute 'namedtoplo'. Did you mean: namedtuple?
(由 Pablo Galindo 在 bpo-38530 中贡献。)
警告 请注意如果未调用 PyErr_Display() 来显示错误则此特性将没有效果,这可能发生在使用了某些其他自定义错误显示函数的时候。 这在某些 REPL 例如 IPython 上是一种常见的情况。
当打印解释器所引发的 NameError 时,PyErr_Display() 将提供引发异常的函数中类似变量名称的建议:
>>>
>>> schwarzschild_black_hole = None
>>> schwarschild_black_hole
Traceback (most recent call last):
File "", line 1, in
NameError: name 'schwarschild_black_hole' is not defined. Did you mean: schwarzschild_bl
PEP 626 带来了更精确可靠的行号用于调试、性能分析和测试工具。 所有被执行的代码行都会并且只有被执行的代码行才会生成带有正确行号的追踪事件。
帧对象的 f_lineno 属性将总是包含预期的行号。
代码对象的 co_lnotab 属性已被弃用并将在 3.12 中被移除。 需要从偏移量转换为行号的代码应改用新的 co_lines() 方法。
增加了采用模式加上相应动作的 match 语句 和 case 语句 的形式的结构化模式匹配。 模式由序列、映射、基本数据类型以及类实例构成。 模式匹配使得程序能够从复杂的数据类型中提取信息、根据数据结构实现分支,并基于不同的数据形式应用特定的动作。
模式匹配的通用语法如下:
match subject:
case :
case :
case :
case _:
match 语句接受一个表达式并将其值与以一个或多个 case 语句块形式给出的一系列模式进行比较。 具体来说,模式匹配的操作如下:
1 使用具有特定类型和形状的数据 (subject)
2 针对 subject 在 match 语句中求值
3 从上到下对 subject 与 case 语句中的每个模式进行比较直到确认匹配到一个模式。
4 执行与被确认匹配的模式相关联的动作。
5 如果没有确认到一个完全的匹配,则如果提供了使用通配符 _ 的最后一个 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 等绑定就如你在没有 子句的情况下所期望的,而 p2 会绑定目标的整个第二项。as
大多数字面值是按相等性比较的。 但是,单例对象 , 和 则是按标识号比较的。TrueFalseNone
命名常量也可以在模式中使用。 这些命名常量必须为带点号的名称以防止常量被解读为捕获变量:
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 ”)
完整规格说明参见 PEP 634。 动机与理由参见 PEP 635,更详细的教程参见 PEP 636。
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)
为了便于查找此类错误,增加了可选的 。 它会在 sys.flags.warn_default_encoding 为真值并使用了语言区域指定的默认编码格式时被发出。EncodingWarning
增加了 选项和 PYTHONWARNDEFAULTENCODING 来启用相应警告。-X warn_default_encoding
请参阅 文本编码格式 了解更多信息。