帶圓括號的上下文管理器
現在已支持使用外層圓括號來使多個上下文管理器可以連續多行地書寫。 這允許將過長的上下文管理器集能夠以與之前 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.")