8. 複合語句¶
複合語句包含(一組)其他語句;它們以某種方式影響或控制這些其他語句的執行。通常,一個複合語句會跨越多行,雖然在一個簡單的形式中,整個複合語句也可以包含在一行內。
if
、while
和 for
語句實現了傳統的控制流構造。try
語句為一組語句指定異常處理器和/或清理程式碼,而 with
語句允許在一塊程式碼塊前後執行初始化和終結程式碼。函式和類定義在語法上也是複合語句。
一個複合語句由一個或多個“子句”組成。一個子句由一個句頭和一個“程式碼塊”組成。特定複合語句的子句句頭都處於相同的縮排層級。每個子句句頭都以一個唯一標識的關鍵字開始,並以一個冒號結束。程式碼塊是受子句控制的一組語句。程式碼塊可以是在與句頭同一行上,跟在句頭冒號後面的一個或多個以分號分隔的簡單語句,也可以是在後續行上的一個或多個縮排的語句。只有後一種形式的程式碼塊才能包含巢狀的複合語句;以下是無效的,主要是因為它不清楚接下來的 else
子句將屬於哪個 if
子句:
if test1: if test2: print(x)
另外請注意,在這種上下文中,分號的繫結比冒號更緊密,因此在下面的例子中,print()
呼叫要麼全部執行,要麼全不執行:
if x < y < z: print(x); print(y); print(z)
總結如下:
compound_stmt:if_stmt
|while_stmt
|for_stmt
|try_stmt
|with_stmt
|match_stmt
|funcdef
|classdef
|async_with_stmt
|async_for_stmt
|async_funcdef
suite:stmt_list
NEWLINE | NEWLINE INDENTstatement
+ DEDENT statement:stmt_list
NEWLINE |compound_stmt
stmt_list:simple_stmt
(";"simple_stmt
)* [";"]
請注意,語句總是以 NEWLINE
結束,後面可能跟著一個 DEDENT
。還要注意,可選的延續子句總是以一個不能作為語句開頭的關鍵字開始,因此沒有歧義(“懸掛 else
” 問題在 Python 中透過要求巢狀的 if
語句進行縮排來解決)。
為了清晰起見,以下章節中的語法規則格式將每個子句放在單獨的一行。
8.1. if
語句¶
if
語句用於條件執行:
if_stmt: "if"assignment_expression
":"suite
("elif"assignment_expression
":"suite
)* ["else" ":"suite
]
它透過逐一求值表示式,直到找到一個為真的表示式(關於真和假的定義,請參見 布林運算),然後選擇其中一個程式碼塊來執行;然後該程式碼塊被執行(並且 if
語句的其他部分不會被執行或求值)。如果所有表示式都為假,則執行 else
子句的程式碼塊(如果存在)。
8.2. while
語句¶
while
語句用於在表示式為真的情況下重複執行:
while_stmt: "while"assignment_expression
":"suite
["else" ":"suite
]
它會重複測試表達式,如果為真,則執行第一個程式碼塊;如果表示式為假(可能在第一次測試時就為假),則執行 else
子句的程式碼塊(如果存在),然後迴圈終止。
在第一個程式碼塊中執行的 break
語句會終止迴圈,而不會執行 else
子句的程式碼塊。在第一個程式碼塊中執行的 continue
語句會跳過程式碼塊的其餘部分,並返回到測試表達式的地方。
8.3. for
語句¶
for
語句用於迭代一個序列(例如字串、元組或列表)或其他可迭代物件的元素:
for_stmt: "for"target_list
"in"starred_expression_list
":"suite
["else" ":"suite
]
starred_expression_list
表示式只會被求值一次;它應返回一個 可迭代 物件。然後為該可迭代物件建立一個 迭代器。迭代器提供的第一個項會被使用標準的賦值規則(見 賦值語句)賦給目標列表,然後執行程式碼塊。對迭代器提供的每個項都會重複此過程。當迭代器耗盡時,會執行 else
子句中的程式碼塊(如果存在),然後迴圈終止。
在第一個程式碼塊中執行的 break
語句會終止迴圈,而不會執行 else
子句的程式碼塊。在第一個程式碼塊中執行的 continue
語句會跳過程式碼塊的其餘部分,並繼續處理下一個項,或者如果沒有下一個項,則處理 else
子句。
for 迴圈會對目標列表中的變數進行賦值。這會覆蓋對這些變數之前的所有賦值,包括在 for 迴圈的程式碼塊中進行的賦值:
for i in range(10):
print(i)
i = 5 # this will not affect the for-loop
# because i will be overwritten with the next
# index in the range
迴圈結束後,目標列表中的名稱不會被刪除,但如果序列為空,它們將完全不會被迴圈賦值。提示:內建型別 range()
表示不可變的整數算術序列。例如,迭代 range(3)
會依次產生 0、1 和 2。
在 3.11 版本發生變更: 表示式列表中現在允許使用帶星號的元素。
8.4. try
語句¶
try
語句為一組語句指定異常處理器和/或清理程式碼:
try_stmt:try1_stmt
|try2_stmt
|try3_stmt
try1_stmt: "try" ":"suite
("except" [expression
["as"identifier
]] ":"suite
)+ ["else" ":"suite
] ["finally" ":"suite
] try2_stmt: "try" ":"suite
("except" "*"expression
["as"identifier
] ":"suite
)+ ["else" ":"suite
] ["finally" ":"suite
] try3_stmt: "try" ":"suite
"finally" ":"suite
有關異常的更多資訊,請參見 異常 部分,有關使用 raise
語句生成異常的資訊,請參見 raise 語句 部分。
在 3.14 版本發生變更: 支援在使用多個異常型別時可選地省略分組括號。請參閱 PEP 758。
8.4.1. except
子句¶
except
子句指定一個或多個異常處理器。當 try
子句中沒有發生異常時,不會執行任何異常處理器。當 try
程式碼塊中發生異常時,會開始搜尋異常處理器。這個搜尋會依次檢查 except
子句,直到找到一個與異常匹配的子句。一個沒有表示式的 except
子句(如果存在)必須是最後一個;它會匹配任何異常。
對於帶有表示式的 except
子句,該表示式必須求值為一個異常型別或一個異常型別的元組。如果提供了多個異常型別且未使用 as
子句,可以省略括號。丟擲的異常如果是一個類或是異常物件的 非虛擬基類,或者是一個包含該類的元組,那麼這個異常就匹配這個 except
子句,該子句的表示式求值結果就是這個類或元組。
如果沒有 except
子句匹配異常,異常處理器的搜尋將在周圍的程式碼和呼叫棧中繼續進行。[1]
如果在 except
子句的句頭中對錶達式求值時引發了異常,那麼對原處理器的搜尋將被取消,並在周圍程式碼和呼叫棧中開始對新異常的搜尋(這被視為整個 try
語句引發了該異常)。
當找到匹配的 except
子句時,如果該 except
子句中有 as
關鍵字,則異常會被賦給其後指定的目標,然後執行該 except
子句的程式碼塊。所有 except
子句都必須有一個可執行的程式碼塊。當這個程式碼塊執行結束時,執行會在整個 try
語句之後正常繼續。(這意味著如果對於同一個異常存在兩個巢狀的處理器,並且異常發生在內部處理器的 try
子句中,外部處理器將不會處理該異常。)
當一個異常透過 as target
被賦值後,它會在 except
子句的末尾被清除。這就好像
except E as N:
foo
被翻譯成
except E as N:
try:
foo
finally:
del N
這意味著異常必須被賦給一個不同的名稱,以便在 except
子句之後引用它。異常之所以被清除,是因為它們與附帶的回溯資訊一起,與棧幀形成了一個引用迴圈,使得該幀中的所有區域性變數在下一次垃圾回收發生之前都保持活動狀態。
在執行 except
子句的程式碼塊之前,異常被儲存在 sys
模組中,可以透過呼叫 sys.exception()
在 except
子句的主體內訪問它。離開異常處理器時,儲存在 sys
模組中的異常會恢復為其先前的值:
>>> print(sys.exception())
None
>>> try:
... raise TypeError
... except:
... print(repr(sys.exception()))
... try:
... raise ValueError
... except:
... print(repr(sys.exception()))
... print(repr(sys.exception()))
...
TypeError()
ValueError()
TypeError()
>>> print(sys.exception())
None
8.4.2. except*
子句¶
except*
子句指定一個或多個異常組(BaseExceptionGroup
例項)的處理器。一個 try
語句可以有 except
或 except*
子句,但不能兩者都有。在 except*
的情況下,用於匹配的異常型別是強制性的,所以 except*:
是一個語法錯誤。型別的解釋與 except
的情況相同,但匹配是在正在處理的異常組中包含的異常上進行的。如果匹配的型別是 BaseExceptionGroup
的子類,會引發一個 TypeError
,因為這會導致語義模糊。
當在 try 塊中引發一個異常組時,每個 except*
子句會將其(見 split()
)拆分為匹配和不匹配的異常子組。如果匹配的子組非空,它就成為被處理的異常(即 sys.exception()
返回的值),並被賦給 except*
子句的目標(如果存在)。然後,except*
子句的主體被執行。如果不匹配的子組非空,它會被下一個 except*
以同樣的方式處理。這個過程會一直持續,直到異常組中的所有異常都被匹配,或者最後一個 except*
子句已經執行。
在所有 except*
子句執行之後,未處理的異常組將與從 except*
子句內部引發或重新引發的任何異常合併。這個合併後的異常組會繼續傳播。
>>> try:
... raise ExceptionGroup("eg",
... [ValueError(1), TypeError(2), OSError(3), OSError(4)])
... except* TypeError as e:
... print(f'caught {type(e)} with nested {e.exceptions}')
... except* OSError as e:
... print(f'caught {type(e)} with nested {e.exceptions}')
...
caught <class 'ExceptionGroup'> with nested (TypeError(2),)
caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4))
+ Exception Group Traceback (most recent call last):
| File "<doctest default[0]>", line 2, in <module>
| raise ExceptionGroup("eg",
| [ValueError(1), TypeError(2), OSError(3), OSError(4)])
| ExceptionGroup: eg (1 sub-exception)
+-+---------------- 1 ----------------
| ValueError: 1
+------------------------------------
如果從 try
塊中引發的異常不是一個異常組,並且它的型別匹配某個 except*
子句,它會被捕獲並用一個空訊息字串包裝成一個異常組。這確保了目標 e
的型別始終是 BaseExceptionGroup
:
>>> try:
... raise BlockingIOError
... except* BlockingIOError as e:
... print(repr(e))
...
ExceptionGroup('', (BlockingIOError()))
8.4.3. else
子句¶
可選的 else
子句在控制流離開 try
程式碼塊,且沒有引發異常,也沒有執行 return
、continue
或 break
語句時執行。else
子句中的異常不會被前面的 except
子句處理。
8.4.4. finally
子句¶
如果存在 finally
,它指定一個“清理”處理器。try
子句會被執行,包括任何 except
和 else
子句。如果在任何子句中發生異常且未被處理,該異常會被臨時儲存。finally
子句會被執行。如果有一個已儲存的異常,它會在 finally
子句的末尾被重新引發。如果 finally
子句引發了另一個異常,已儲存的異常會被設定為新異常的上下文。如果 finally
子句執行了 return
、break
或 continue
語句,已儲存的異常會被丟棄。例如,這個函式返回 42。
def f():
try:
1/0
finally:
return 42
在執行 finally
子句期間,異常資訊對程式是不可用的。
當在 try
…finally
語句的 try
程式碼塊中執行 return
、break
或 continue
語句時,finally
子句也會在“退出時”執行。
函式的返回值由最後執行的 return
語句確定。由於 finally
子句總是執行,因此在 finally
子句中執行的 return
語句將始終是最後執行的那個。下面的函式返回 'finally'。
def foo():
try:
return 'try'
finally:
return 'finally'
在 3.8 版本發生變更: 在 Python 3.8 之前,由於實現上的問題,continue
語句在 finally
子句中是無效的。
在 3.14 版本發生變更: 當 return
、break
或 continue
出現在 finally
塊中時,編譯器會發出一個 SyntaxWarning
(見 PEP 765)。
8.5. with
語句¶
with
語句用於將程式碼塊的執行包裝在由上下文管理器定義的方法中(見 With 語句上下文管理器)。這使得常見的 try
…except
…finally
使用模式能夠被封裝起來以便於重用。
with_stmt: "with" ( "("with_stmt_contents
","? ")" |with_stmt_contents
) ":"suite
with_stmt_contents:with_item
(","with_item
)* with_item:expression
["as"target
]
帶有一個“專案”的 with
語句的執行過程如下:
對上下文表達式(在
with_item
中給出的表示式)進行求值以獲得一個上下文管理器。載入上下文管理器的
__enter__()
方法以備後用。載入上下文管理器的
__exit__()
方法以備後用。呼叫上下文管理器的
__enter__()
方法。如果
with
語句中包含一個目標,那麼__enter__()
的返回值將被賦給它。備註
with
語句保證,如果__enter__()
方法無錯誤地返回,那麼__exit__()
將總是被呼叫。因此,如果在對目標列表賦值期間發生錯誤,它將被視為與在程式碼塊內部發生的錯誤一樣。請參見下面的第 7 步。程式碼塊被執行。
呼叫上下文管理器的
__exit__()
方法。如果異常導致程式碼塊退出,其型別、值和回溯資訊將作為引數傳遞給__exit__()
。否則,將提供三個None
引數。如果程式碼塊是由於異常而退出的,並且
__exit__()
方法的返回值為假,則異常會被重新引發。如果返回值為真,則異常被抑制,執行將繼續到with
語句之後的語句。如果程式碼塊由於除異常之外的任何原因退出,
__exit__()
的返回值將被忽略,執行將在所採取的退出型別的正常位置繼續進行。
以下程式碼:
with EXPRESSION as TARGET:
SUITE
在語義上等同於:
manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False
try:
TARGET = value
SUITE
except:
hit_except = True
if not exit(manager, *sys.exc_info()):
raise
finally:
if not hit_except:
exit(manager, None, None, None)
如果有多個專案,上下文管理器的處理方式就像嵌套了多個 with
語句:
with A() as a, B() as b:
SUITE
在語義上等同於:
with A() as a:
with B() as b:
SUITE
如果專案被括號包圍,你也可以將多專案的上下文管理器寫在多行上。例如:
with (
A() as a,
B() as b,
):
SUITE
在 3.1 版本發生變更: 支援多個上下文表達式。
在 3.10 版本發生變更: 支援使用分組括號將語句拆分到多行。
8.6. match
語句¶
在 3.10 版本加入。
match 語句用於模式匹配。語法:
match_stmt: 'match'subject_expr
":" NEWLINE INDENTcase_block
+ DEDENT subject_expr: `!star_named_expression` "," `!star_named_expressions`? | `!named_expression` case_block: 'case'patterns
[guard
] ":" `!block`
備註
本節使用單引號來表示軟關鍵字。
模式匹配接受一個模式作為輸入(跟在 case
之後)和一個主題值(跟在 match
之後)。模式(可能包含子模式)將與主題值進行匹配。結果是:
匹配成功或失敗(也稱為模式成功或失敗)。
可能將匹配到的值繫結到一個名稱。其先決條件將在下文進一步討論。
match
和 case
關鍵字是軟關鍵字。
8.6.1. 概述¶
以下是 match 語句的邏輯流程概述:
對主題表示式
subject_expr
進行求值,並獲得一個結果主題值。如果主題表示式包含一個逗號,則使用標準規則構造一個元組。嘗試將
case_block
中的每個模式與主題值進行匹配。成功或失敗的具體規則在下文描述。匹配嘗試還可以將模式中的部分或全部獨立名稱進行繫結。精確的模式繫結規則因模式型別而異,並在下文指定。在成功的模式匹配期間進行的名稱繫結會存活到被執行的程式碼塊之外,並可以在 match 語句之後使用。備註
在失敗的模式匹配期間,某些子模式可能會成功。不要依賴於為失敗的匹配進行的繫結。反之,也不要依賴於變數在失敗的匹配後保持不變。確切的行為取決於實現,可能會有所不同。這是一個有意的決定,旨在允許不同的實現新增最佳化。
如果模式成功,則對相應的守護條件(如果存在)進行求值。在這種情況下,可以保證所有名稱繫結都已經發生。
如果守護條件求值為真或缺失,則執行
case_block
內的block
。否則,如上所述嘗試下一個
case_block
。如果沒有更多的 case 塊,match 語句完成。
備註
使用者通常不應依賴於模式被求值。根據實現的不同,直譯器可能會快取值或使用其他最佳化來跳過重複的求值。
一個 match 語句示例:
>>> flag = False
>>> match (100, 200):
... case (100, 300): # Mismatch: 200 != 300
... print('Case 1')
... case (100, 200) if flag: # Successful match, but guard fails
... print('Case 2')
... case (100, y): # Matches and binds y to 200
... print(f'Case 3, y: {y}')
... case _: # Pattern not attempted
... print('Case 4, I match anything!')
...
Case 3, y: 200
在這種情況下,if flag
是一個守護條件。更多相關內容請閱讀下一節。
8.6.2. 守護條件¶
guard: "if" `!named_expression`
guard
(它是 case
的一部分)必須成功,case
塊內的程式碼才能執行。它的形式是:if
後跟一個表示式。
帶有 guard
的 case
塊的邏輯流程如下:
檢查
case
塊中的模式是否成功。如果模式失敗,則不求值guard
,並檢查下一個case
塊。如果模式成功,則求值
guard
。如果
guard
條件求值為真,則選擇該 case 塊。如果
guard
條件求值為假,則不選擇該 case 塊。如果
guard
在求值期間引發異常,則異常會向上冒泡。
守護條件允許有副作用,因為它們是表示式。守護條件的求值必須從第一個到最後一個 case 塊,一次一個地進行,跳過其模式並非全部成功的 case 塊。(即,守護條件的求值必須按順序進行。)一旦一個 case 塊被選中,守護條件的求值必須停止。
8.6.3. 不可反駁的 case 塊¶
一個不可反駁的 case 塊是一個匹配所有的 case 塊。一個 match 語句最多可以有一個不可反駁的 case 塊,並且它必須是最後一個。
如果一個 case 塊沒有守護條件並且其模式是不可反駁的,那麼它被認為是不可反駁的。如果我們可以僅從其語法證明一個模式總是會成功,那麼它被認為是不可反駁的。只有以下模式是不可反駁的:
8.6.4. 模式¶
備註
本節使用的語法符號超出了標準 EBNF 的範圍:
符號
SEP.RULE+
是RULE (SEP RULE)*
的簡寫。符號
!RULE
是一個否定前向斷言的簡寫。
patterns
的頂層語法是:
patterns:open_sequence_pattern
|pattern
pattern:as_pattern
|or_pattern
closed_pattern: |literal_pattern
|capture_pattern
|wildcard_pattern
|value_pattern
|group_pattern
|sequence_pattern
|mapping_pattern
|class_pattern
下面的描述將包括一個“通俗地講”的描述,說明一個模式的作用,以作說明之用(感謝 Raymond Hettinger 的一份文件,它啟發了大部分描述)。請注意,這些描述純粹是為了說明目的,可能不反映底層實現。此外,它們不涵蓋所有有效的形式。
8.6.4.1. OR 模式¶
OR 模式是由豎線 |
分隔的兩個或多個模式。語法:
or_pattern: "|".closed_pattern
+
只有最後一個子模式可以是不可反駁的,並且每個子模式必須繫結相同的名稱集合,以避免歧義。
OR 模式會依次將其每個子模式與主題值進行匹配,直到有一個成功。這時 OR 模式被認為是成功的。否則,如果所有子模式都失敗,則 OR 模式失敗。
通俗地講,P1 | P2 | ...
將嘗試匹配 P1
,如果失敗,它將嘗試匹配 P2
,只要有一個成功就立即成功,否則失敗。
8.6.4.2. AS 模式¶
AS 模式會將 as
關鍵字左側的 OR 模式與一個主題進行匹配。語法:
as_pattern:or_pattern
"as"capture_pattern
如果 OR 模式失敗,則 AS 模式失敗。否則,AS 模式將主題繫結到 as 關鍵字右側的名稱併成功。capture_pattern
不能是 _
。
通俗地講,P as NAME
將與 P
匹配,成功時它將設定 NAME = <subject>
。
8.6.4.3. 字面值模式¶
字面值模式對應於 Python 中的大多數字面值。語法:
literal_pattern:signed_number
|signed_number
"+" NUMBER |signed_number
"-" NUMBER |strings
| "None" | "True" | "False" signed_number: ["-"] NUMBER
規則 strings
和標記 NUMBER
在標準 Python 語法中定義。支援三引號字串。支援原始字串和位元組字串。不支援f-字串和t-字串。
signed_number '+' NUMBER
和 signed_number '-' NUMBER
這兩種形式用於表示複數;它們要求左邊是實數,右邊是虛數。例如 3 + 4j
。
通俗地講,LITERAL
只有在 <subject> == LITERAL
時才會成功。對於單例 None
、True
和 False
,會使用 is
運算子。
8.6.4.4. 捕獲模式¶
捕獲模式將主題值繫結到一個名稱。語法:
capture_pattern: !'_' NAME
單個下劃線 _
不是捕獲模式(這正是 !'_'
所表達的)。它被視為一個wildcard_pattern
。
在給定的模式中,一個給定的名稱只能被繫結一次。例如,case x, x: ...
是無效的,而 case [x] | x: ...
是允許的。
捕獲模式總是成功。繫結遵循 PEP 572 中賦值表示式運算子建立的作用域規則;除非有適用的 global
或 nonlocal
語句,否則該名稱成為最近的包含函式作用域中的區域性變數。
通俗地講,NAME
將總是成功,並且它將設定 NAME = <subject>
。
8.6.4.5. 萬用字元模式¶
萬用字元模式總是成功(匹配任何東西)並且不繫結任何名稱。語法:
wildcard_pattern: '_'
_
在任何模式中都是一個軟關鍵字,但僅限於在模式中。即使在 match
主題表示式、guard
和 case
塊中,它也像往常一樣是一個識別符號。
通俗地講,_
將總是成功。
8.6.4.6. 值模式¶
值模式表示 Python 中的一個命名值。語法:
value_pattern:attr
attr:name_or_attr
"." NAME name_or_attr:attr
| NAME
模式中的帶點名稱使用標準的 Python 名稱解析規則進行查詢。如果找到的值與主題值比較相等(使用 ==
相等運算子),則模式成功。
通俗地講,NAME1.NAME2
只有在 <subject> == NAME1.NAME2
時才會成功。
備註
如果同一個值在同一個 match 語句中多次出現,直譯器可能會快取找到的第一個值並重用它,而不是重複相同的查詢。這個快取嚴格綁定於給定的 match 語句的某一次執行。
8.6.4.7. 分組模式¶
分組模式允許使用者在模式周圍新增括號,以強調預期的分組。除此之外,它沒有額外的語法。語法:
group_pattern: "(" pattern
")"
通俗地講,(P)
的效果與 P
相同。
8.6.4.8. 序列模式¶
序列模式包含多個子模式,用於與序列元素進行匹配。其語法類似於列表或元組的解包。
sequence_pattern: "[" [maybe_sequence_pattern
] "]" | "(" [open_sequence_pattern
] ")" open_sequence_pattern:maybe_star_pattern
"," [maybe_sequence_pattern
] maybe_sequence_pattern: ",".maybe_star_pattern
+ ","? maybe_star_pattern:star_pattern
|pattern
star_pattern: "*" (capture_pattern
|wildcard_pattern
)
對於序列模式,使用圓括號或方括號沒有區別(即 (...)
vs [...]
)。
備註
一個被圓括號包圍且沒有尾隨逗號的單個模式(例如 (3 | 4)
)是一個分組模式。而一個被方括號包圍的單個模式(例如 [3 | 4]
)仍然是一個序列模式。
一個序列模式中最多可以有一個星號子模式。星號子模式可以出現在任何位置。如果沒有星號子模式,該序列模式是固定長度的序列模式;否則它是可變長度的序列模式。
以下是將序列模式與主題值進行匹配的邏輯流程:
如果主題值不是一個序列[2],則序列模式失敗。
如果主題值是
str
、bytes
或bytearray
的例項,則序列模式失敗。後續步驟取決於序列模式是固定長度還是可變長度。
如果序列模式是固定長度的:
如果主題序列的長度不等於子模式的數量,則序列模式失敗。
序列模式中的子模式會從左到右與其在主題序列中的對應項進行匹配。只要有一個子模式失敗,匹配就會停止。如果所有子模式都成功匹配其對應項,則序列模式成功。
否則,如果序列模式是可變長度的:
如果主題序列的長度小於非星號子模式的數量,則序列模式失敗。
前導的非星號子模式會像固定長度序列一樣與其對應項進行匹配。
如果前一步成功,星號子模式會匹配一個由剩餘的主題項組成的列表,不包括星號子模式後面對應的非星號子模式的剩餘項。
剩餘的非星號子模式會像固定長度序列一樣與其對應的主題項進行匹配。
通俗地講,[P1, P2, P3,
… , P<N>]
只有在以下所有情況都發生時才匹配:
檢查
<subject>
是否為序列len(subject) == <N>
P1
匹配<subject>[0]
(注意這個匹配也可以繫結名稱)P2
匹配<subject>[1]
(注意這個匹配也可以繫結名稱)……以此類推,對於相應的模式/元素。
8.6.4.9. 對映模式¶
對映模式包含一個或多個鍵-值模式。其語法類似於字典的構造。語法:
mapping_pattern: "{" [items_pattern
] "}" items_pattern: ",".key_value_pattern
+ ","? key_value_pattern: (literal_pattern
|value_pattern
) ":"pattern
|double_star_pattern
double_star_pattern: "**"capture_pattern
一個對映模式中最多可以有一個雙星號模式。雙星號模式必須是對映模式中的最後一個子模式。
對映模式中不允許有重複的鍵。重複的字面值鍵會引發 SyntaxError
。兩個值相同的鍵會在執行時引發 ValueError
。
以下是將對映模式與主題值進行匹配的邏輯流程:
如果主題值不是一個對映[3],則對映模式失敗。
如果對映模式中給出的每個鍵都存在於主題對映中,並且每個鍵的模式都與主題對映的相應項匹配,則對映模式成功。
如果在對映模式中檢測到重複的鍵,則該模式被視為無效。對於重複的字面值,會引發
SyntaxError
;對於值相同的命名鍵,會引發ValueError
。
備註
鍵-值對使用對映主題的 get()
方法的雙引數形式進行匹配。匹配的鍵-值對必須已經存在於對映中,而不是透過 __missing__()
或 __getitem__()
動態建立的。
通俗地講,{KEY1: P1, KEY2: P2, ... }
只有在以下所有情況都發生時才匹配:
檢查
<subject>
是否為對映KEY1 in <subject>
P1
匹配<subject>[KEY1]
……以此類推,對於相應的鍵/模式對。
8.6.4.10. 類模式¶
類模式表示一個類及其位置引數和關鍵字引數(如果有)。語法:
class_pattern:name_or_attr
"(" [pattern_arguments
","?] ")" pattern_arguments:positional_patterns
[","keyword_patterns
] |keyword_patterns
positional_patterns: ",".pattern
+ keyword_patterns: ",".keyword_pattern
+ keyword_pattern: NAME "="pattern
在類模式中不應重複使用相同的關鍵字。
以下是將類模式與主題值進行匹配的邏輯流程:
如果主題值不是
name_or_attr
的例項(透過isinstance()
測試),則類模式失敗。如果沒有模式引數,則模式成功。否則,後續步驟取決於是否存在關鍵字或位置引數模式。
對於一些內建型別(在下面指定),接受一個位置子模式,該子模式將匹配整個主題;對於這些型別,關鍵字模式也像其他型別一樣工作。
如果只有關鍵字模式,它們會按以下方式逐一處理:
I. 關鍵字在主題上作為屬性被查詢。
如果這引發了除
AttributeError
之外的異常,則異常會向上冒泡。如果這引發了
AttributeError
,則類模式失敗。否則,與關鍵字模式關聯的子模式會與主題的屬性值進行匹配。如果失敗,類模式失敗;如果成功,匹配繼續到下一個關鍵字。
II. 如果所有關鍵字模式都成功,則類模式成功。
如果存在任何位置模式,它們會使用類
name_or_attr
上的__match_args__
屬性在匹配前轉換為關鍵字模式:I. 呼叫等同於
getattr(cls, "__match_args__", ())
的操作。- II. 一旦所有位置模式都已轉換為關鍵字模式,
匹配就像只有關鍵字模式一樣繼續進行。
對於以下內建型別,位置子模式的處理方式不同:
這些類接受單個位置引數,並且該處的模式與整個物件匹配,而不是與屬性匹配。例如
int(0|1)
匹配值0
,但不匹配值0.0
。
通俗地講,CLS(P1, attr=P2)
只有在以下所有情況都發生時才匹配:
isinstance(<subject>, CLS)
使用
CLS.__match_args__
將P1
轉換為關鍵字模式對於每個關鍵字引數
attr=P2
:hasattr(<subject>, "attr")
P2
匹配<subject>.attr
……以此類推,對於相應的關鍵字引數/模式對。
8.7. 函式定義¶
函式定義定義了一個使用者自定義函式物件(見 標準型別層級):
funcdef: [decorators
] "def"funcname
[type_params
] "(" [parameter_list
] ")" ["->"expression
] ":"suite
decorators:decorator
+ decorator: "@"assignment_expression
NEWLINE parameter_list:defparameter
(","defparameter
)* "," "/" ["," [parameter_list_no_posonly
]] |parameter_list_no_posonly
parameter_list_no_posonly:defparameter
(","defparameter
)* ["," [parameter_list_starargs
]] |parameter_list_starargs
parameter_list_starargs: "*" [star_parameter
] (","defparameter
)* ["," [parameter_star_kwargs
]] | "*" (","defparameter
)+ ["," [parameter_star_kwargs
]] |parameter_star_kwargs
parameter_star_kwargs: "**"parameter
[","] parameter:identifier
[":"expression
] star_parameter:identifier
[":" ["*"]expression
] defparameter:parameter
["="expression
] funcname:identifier
函式定義是一個可執行的語句。它的執行會將函式名繫結到當前區域性名稱空間中的一個函式物件(一個圍繞函式可執行程式碼的包裝器)。這個函式物件包含一個對當前全域性名稱空間的引用,作為函式被呼叫時要使用的全域性名稱空間。
函式定義不會執行函式體;這隻有在函式被呼叫時才會執行。[4]
函式定義可以被一個或多個裝飾器表示式包裝。裝飾器表示式在函式定義時被求值,在包含函式定義的範圍內。結果必須是一個可呼叫物件,它會以函式物件作為唯一引數被呼叫。返回的值會繫結到函式名,而不是函式物件本身。多個裝飾器以巢狀方式應用。例如,以下程式碼:
@f1(arg)
@f2
def func(): pass
大致等同於:
def func(): pass
func = f1(arg)(f2(func))
只是原始函式不會臨時繫結到名稱 func
。
在 3.9 版本發生變更: 函式可以用任何有效的 assignment_expression
來裝飾。以前,語法要嚴格得多;詳見 PEP 614。
可以在函式名和其引數列表的左括號之間用方括號給出一個型別形參列表。這向靜態型別檢查器表明該函式是泛型的。在執行時,型別形參可以從函式的 __type_params__
屬性中檢索。更多資訊請參見泛型函式。
在 3.12 版本發生變更: 型別形參列表是 Python 3.12 中的新功能。
當一個或多個 形參 具有 parameter =
expression 這樣的形式時,該函式就被稱為具有“預設形參值”。對於有預設值的形參,在呼叫中對應的 實參 可以被省略,此時將使用形參的預設值。如果一個形參有預設值,那麼在它之後直到 “*
” 為止的所有形參都必須有預設值 —— 這是一個句法限制,並未在語法中明確表達。
預設形參值在函式定義被執行時,會從左至右地進行求值。 這意味著表示式只在函式定義時被求值一次,並且每次呼叫都使用同一個“預計算”的值。當預設形參值是一個可變物件(例如列表或字典)時,理解這一點尤為重要:如果函式修改了該物件(例如透過向列表附加一個項),那麼預設形參值實際上也被修改了。這通常不是我們所期望的。一種解決方法是使用 None
作為預設值,並在函式體中顯式地對其進行測試,例如:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
函式呼叫語義的更多細節在 呼叫 一節中有詳細描述。函式呼叫總是會為形參列表中的所有形參賦值,這些值可能來自位置實參、關鍵字實參或預設值。如果出現 “*identifier
” 形式,它會被初始化為一個元組,接收任何多餘的位置形參,預設為空元組。如果出現 “**identifier
” 形式,它會被初始化為一個新的有序對映,接收任何多餘的關鍵字實參,預設為一個同類型的新空對映。“*
” 或 “*identifier
” 之後的形參是僅關鍵字形參,只能透過關鍵字實參來傳遞。“/
” 之前的形參是僅位置形參,只能透過位置實參來傳遞。
在 3.8 版更改: 可以使用 /
函式形參語法來指明僅位置形參。詳情參見 PEP 570。
形參可以在形參名稱後帶有 “: expression
” 形式的 標註。任何形參都可以有標註,即使是 *identifier
或 **identifier
這種形式的形參也不例外。(作為一個特例,*identifier
形式的形參可以有 “: *expression
” 形式的標註。)函式可以在形參列表後帶有 “-> expression
” 形式的“返回”標註。這些標註可以是任何有效的 Python 表示式。標註的存在不會改變函式的語義。有關標註的更多資訊,請參閱 標註。
在 3.11 版更改: “*identifier
” 形式的形參可以有 “: *expression
” 形式的標註。參見 PEP 646。
也可以建立匿名函式(未繫結到名稱的函式),以便在表示式中直接使用。這需要使用 lambda 表示式,在 Lambda 表示式 一節中有描述。請注意,lambda 表示式僅僅是簡化函式定義的一種簡寫;在 “def
” 語句中定義的函式可以像 lambda 表示式定義的函式一樣被傳遞或賦值給其他名稱。“def
” 形式實際上更強大,因為它允許執行多條語句和標註。
程式設計師注意: 函式是一級物件。在函式定義內部執行的 “def
” 語句會定義一個區域性函式,該函式可以被返回或傳遞。巢狀函式中使用的自由變數可以訪問包含該 def 語句的函式的區域性變數。詳情參見 命名與繫結 一節。
8.8. 類定義¶
類定義會定義一個類物件(參見 標準型別層級 一節)
classdef: [decorators
] "class"classname
[type_params
] [inheritance
] ":"suite
inheritance: "(" [argument_list
] ")" classname:identifier
類定義是可執行語句。繼承列表通常給出一列基類(更高階的用法請參見 元類),因此列表中的每個項都應求值為一個允許子類化的類物件。沒有繼承列表的類預設繼承自基類 object
;因此,
class Foo:
pass
等價於
class Foo(object):
pass
然後,該類的程式碼塊在一個新的執行幀中執行(參見 命名與繫結),使用一個新建立的區域性名稱空間和原始的全域性名稱空間。(通常,該程式碼塊主要包含函式定義。)當該類的程式碼塊執行完畢後,其執行幀被丟棄,但其區域性名稱空間被儲存下來。[5] 接著,使用繼承列表作為基類,以及儲存的區域性名稱空間作為屬性字典,來建立一個類物件。類名在原始的區域性名稱空間中被繫結到這個類物件。
在類主體中定義屬性的順序被保留在新類的 __dict__
中。請注意,這僅在類建立後立即是可靠的,並且僅適用於使用定義語法定義的類。
可以使用 元類 對類的建立過程進行深度定製。
類也可以被裝飾:就像裝飾函式一樣,
@f1(arg)
@f2
class Foo: pass
大致等同於:
class Foo: pass
Foo = f1(arg)(f2(Foo))
裝飾器表示式的求值規則與函式裝飾器相同。結果隨後被繫結到類名。
在 3.9 版更改: 類可以用任何有效的 assignment_expression
來裝飾。以前,語法限制要嚴格得多;詳情請參閱 PEP 614。
可以在類名後緊跟的方括號內給出一個 型別形參 列表。這對靜態型別檢查器表明該類是泛型類。在執行時,型別形參可以從類的 __type_params__
屬性中獲取。更多資訊請參見 泛型類。
在 3.12 版本發生變更: 型別形參列表是 Python 3.12 中的新功能。
程式設計師注意: 在類定義中定義的變數是類屬性;它們由例項共享。例項屬性可以在方法中使用 self.name = value
來設定。類屬性和例項屬性都可以透過 “self.name
” 這種表示法來訪問,當以這種方式訪問時,同名的例項屬性會隱藏類屬性。類屬性可以用作例項屬性的預設值,但在此處使用可變值可能導致意外結果。可以使用 描述器 來建立具有不同實現細節的例項變數。
8.9. 協程¶
在 3.5 版本加入。
8.9.1. 協程函式定義¶
async_funcdef: [decorators
] "async" "def"funcname
"(" [parameter_list
] ")" ["->"expression
] ":"suite
Python 協程的執行可以在許多點上被掛起和恢復(參見 協程)。await
表示式、async for
和 async with
只能在協程函式的主體中使用。
使用 async def
語法定義的函式總是協程函式,即使它們不包含 await
或 async
關鍵字。
在協程函式的主體中使用 yield from
表示式會引發 SyntaxError
。
一個協程函式的例子
async def func(param1, param2):
do_stuff()
await some_coroutine()
在 3.7 版更改: await
和 async
現在是關鍵字;以前它們僅在協程函式的主體內部被當作關鍵字處理。
8.9.2. async for
語句¶
async_for_stmt: "async" for_stmt
一個非同步可迭代物件提供一個 __aiter__
方法,該方法直接返回一個非同步迭代器,它可以在其 __anext__
方法中呼叫非同步程式碼。
async for
語句允許方便地遍歷非同步可迭代物件。
以下程式碼:
async for TARGET in ITER:
SUITE
else:
SUITE2
在語義上等價於
iter = (ITER)
iter = type(iter).__aiter__(iter)
running = True
while running:
try:
TARGET = await type(iter).__anext__(iter)
except StopAsyncIteration:
running = False
else:
SUITE
else:
SUITE2
詳情另請參閱 __aiter__()
和 __anext__()
。
在協程函式主體之外使用 async for
語句會引發 SyntaxError
。
8.9.3. async with
語句¶
async_with_stmt: "async" with_stmt
一個非同步上下文管理器是一種上下文管理器,它能夠在 enter 和 exit 方法中掛起執行。
以下程式碼:
async with EXPRESSION as TARGET:
SUITE
在語義上等同於:
manager = (EXPRESSION)
aenter = type(manager).__aenter__
aexit = type(manager).__aexit__
value = await aenter(manager)
hit_except = False
try:
TARGET = value
SUITE
except:
hit_except = True
if not await aexit(manager, *sys.exc_info()):
raise
finally:
if not hit_except:
await aexit(manager, None, None, None)
詳情另請參閱 __aenter__()
和 __aexit__()
。
在協程函式主體之外使用 async with
語句會引發 SyntaxError
。
參見
- PEP 492 - 使用 async 和 await 語法的協程
該提案將協程變為 Python 中一個適當的獨立概念,並增加了相應的支援語法。
8.10. 型別形參列表¶
3.12 新版功能.
在 3.13 版更改: 增加了對預設值的支援(見 PEP 696)。
type_params: "["type_param
(","type_param
)* "]" type_param:typevar
|typevartuple
|paramspec
typevar:identifier
(":"expression
)? ("="expression
)? typevartuple: "*"identifier
("="expression
)? paramspec: "**"identifier
("="expression
)?
def max[T](args: list[T]) -> T:
...
async def amax[T](args: list[T]) -> T:
...
class Bag[T]:
def __iter__(self) -> Iterator[T]:
...
def add(self, arg: T) -> None:
...
type ListOrSet[T] = list[T] | set[T]
在語義上,這表示該函式、類或類型別名是關於某個型別變數的泛型。這些資訊主要由靜態型別檢查器使用,而在執行時,泛型物件的行為與其非泛型對應物非常相似。
型別形參在函式、類或類型別名名稱後的方括號([]
)中宣告。型別形參在泛型物件的作用域內可訪問,但在其他地方不可訪問。因此,在宣告 def func[T](): pass
之後,名稱 T
在模組作用域內是不可用的。下面將更精確地描述泛型物件的語義。型別形參的作用域透過一個特殊的函式(技術上是一個標註作用域)來建模,該函式包裝了泛型物件的建立過程。
泛型函式、類和類型別名有一個 __type_params__
屬性,列出了它們的型別形參。
型別形參有三種:
typing.TypeVar
,由一個普通名稱引入(例如T
)。語義上,它對型別檢查器表示單個型別。typing.TypeVarTuple
,由一個字首為單個星號的名稱引入(例如*Ts
)。語義上,它代表一個包含任意數量型別的元組。typing.ParamSpec
,由一個字首為兩個星號的名稱引入(例如**P
)。語義上,它代表一個可呼叫物件的引數。
typing.TypeVar
宣告可以使用冒號(:
)後跟一個表示式來定義*上界*和*約束*。冒號後的單個表示式表示一個上界(例如 T: int
)。語義上,這意味著該 typing.TypeVar
只能代表該上界的子型別。冒號後用圓括號括起來的表示式元組表示一組約束(例如 T: (str, bytes)
)。元組的每個成員都應該是一個型別(同樣,這在執行時不會被強制執行)。受約束的型別變數只能取約束列表中的型別之一。
對於使用型別形參列表語法宣告的 typing.TypeVar
s,其上界和約束在泛型物件建立時不會被求值,而只有在透過 __bound__
和 __constraints__
屬性顯式訪問其值時才會被求值。為實現這一點,上界或約束會在一個單獨的標註作用域中被求值。
typing.TypeVarTuple
s 和 typing.ParamSpec
s 不能有上界或約束。
所有三種類型的型別形參都可以有一個*預設值*,當型別形參沒有被顯式提供時會使用該值。透過追加一個等號(=
)和一個表示式來新增。與型別變數的上界和約束一樣,預設值在物件建立時不會被求值,而只在訪問型別形參的 __default__
屬性時才被求值。為此,預設值會在一個單獨的標註作用域中被求值。如果沒有為型別形參指定預設值,__default__
屬性會被設定為特殊的哨兵物件 typing.NoDefault
。
以下示例展示了所有允許的型別形參宣告:
def overly_generic[
SimpleTypeVar,
TypeVarWithDefault = int,
TypeVarWithBound: int,
TypeVarWithConstraints: (str, bytes),
*SimpleTypeVarTuple = (int, float),
**SimpleParamSpec = (str, bytearray),
](
a: SimpleTypeVar,
b: TypeVarWithDefault,
c: TypeVarWithBound,
d: Callable[SimpleParamSpec, TypeVarWithConstraints],
*e: SimpleTypeVarTuple,
): ...
8.10.1. 泛型函式¶
泛型函式宣告如下:
def func[T](arg: T): ...
這個語法等價於:
annotation-def TYPE_PARAMS_OF_func():
T = typing.TypeVar("T")
def func(arg: T): ...
func.__type_params__ = (T,)
return func
func = TYPE_PARAMS_OF_func()
這裡的 annotation-def
指示了一個標註作用域,它在執行時實際上並未繫結到任何名稱。(在轉換中還做了一個變通:語法不是透過對 typing
模組進行屬性訪問,而是直接建立了 typing.TypeVar
的一個例項。)
泛型函式的標註在用於宣告型別形參的標註作用域內求值,但函式的預設值和裝飾器則不是。
以下示例闡釋了這些情況以及其他型別形參形式的作用域規則:
@decorator
def func[T: int, *Ts, **P](*args: *Ts, arg: Callable[P, T] = some_default):
...
DEFAULT_OF_arg = some_default
annotation-def TYPE_PARAMS_OF_func():
annotation-def BOUND_OF_T():
return int
# In reality, BOUND_OF_T() is evaluated only on demand.
T = typing.TypeVar("T", bound=BOUND_OF_T())
Ts = typing.TypeVarTuple("Ts")
P = typing.ParamSpec("P")
def func(*args: *Ts, arg: Callable[P, T] = DEFAULT_OF_arg):
...
func.__type_params__ = (T, Ts, P)
return func
func = decorator(TYPE_PARAMS_OF_func())
像 DEFAULT_OF_arg
這樣的大寫名稱在執行時實際上沒有被繫結。
8.10.2. 泛型類¶
泛型類宣告如下:
class Bag[T]: ...
這個語法等價於:
annotation-def TYPE_PARAMS_OF_Bag():
T = typing.TypeVar("T")
class Bag(typing.Generic[T]):
__type_params__ = (T,)
...
return Bag
Bag = TYPE_PARAMS_OF_Bag()
這裡,annotation-def
(不是一個真正的關鍵字)再次表示一個標註作用域,而名稱 TYPE_PARAMS_OF_Bag
在執行時實際上並未被繫結。
泛型類隱式地繼承自 typing.Generic
。泛型類的基類和關鍵字引數在型別形參的型別作用域內求值,而裝飾器則在該作用域之外求值。以下示例說明了這一點:
@decorator
class Bag(Base[T], arg=T): ...
這等價於:
annotation-def TYPE_PARAMS_OF_Bag():
T = typing.TypeVar("T")
class Bag(Base[T], typing.Generic[T], arg=T):
__type_params__ = (T,)
...
return Bag
Bag = decorator(TYPE_PARAMS_OF_Bag())
8.10.3. 泛型類型別名¶
type
語句也可以用來建立泛型類型別名:
type ListOrSet[T] = list[T] | set[T]
除了對值的延遲求值外,這等價於:
annotation-def TYPE_PARAMS_OF_ListOrSet():
T = typing.TypeVar("T")
annotation-def VALUE_OF_ListOrSet():
return list[T] | set[T]
# In reality, the value is lazily evaluated
return typing.TypeAliasType("ListOrSet", VALUE_OF_ListOrSet(), type_params=(T,))
ListOrSet = TYPE_PARAMS_OF_ListOrSet()
這裡,annotation-def
(不是一個真正的關鍵字)表示一個標註作用域。像 TYPE_PARAMS_OF_ListOrSet
這樣的大寫名稱在執行時實際上並未被繫結。
8.11. 標註¶
在 3.14 版更改: 標註現在預設是延遲求值的。
變數和函式形參可以帶有標註,透過在名稱後新增冒號和一個表示式來建立:
x: annotation = 1
def f(param: annotation): ...
函式也可以在箭頭後帶有一個返回標註:
def f() -> annotation: ...
標註通常用於型別提示,但這並非由語言強制執行,並且通常標註可以包含任意表達式。標註的存在不會改變程式碼的執行時語義,除非使用了某種機制來內省和使用這些標註(例如 dataclasses
或 functools.singledispatch()
)。
預設情況下,標註會在一個標註作用域內延遲求值。這意味著它們在包含標註的程式碼被求值時不會被求值。相反,直譯器會儲存資訊,以便在需要時稍後求值該標註。annotationlib
模組提供了用於求值標註的工具。
如果存在future 語句 from __future__ import annotations
,則所有標註都會被儲存為字串:
>>> from __future__ import annotations
>>> def f(param: annotation): ...
>>> f.__annotations__
{'param': 'annotation'}
這個 future 語句將在未來版本的 Python 中被棄用並移除,但不會早於 Python 3.13 的生命週期結束(參見 PEP 749)。當使用它時,像 annotationlib.get_annotations()
和 typing.get_type_hints()
這樣的內省工具在執行時解析標註的可能性會降低。
腳註