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
。還要注意,可選的延續子句總是以一個不能作為語句開頭的關鍵字開頭,因此不存在歧義(Python 透過要求巢狀的 if
語句必須縮排來解決“懸空 else
”問題)。
為了清晰起見,以下各節中的語法規則的格式將每個子句放在單獨的行上。
8.1. if
語句¶
if
語句用於條件執行。
if_stmt ::= "if"assignment_expression
":"suite
("elif"assignment_expression
":"suite
)* ["else" ":"suite
]
它透過逐個評估表示式來選擇恰好一個套件,直到找到一個為 true 的表示式為止(有關 true 和 false 的定義,請參閱 布林運算);然後執行該套件(並且不執行或評估 if
語句的其他部分)。如果所有表示式都為 false,則執行 else
子句(如果存在)的套件。
8.2. while
語句¶
while
語句用於只要表示式為 true 就重複執行。
while_stmt ::= "while"assignment_expression
":"suite
["else" ":"suite
]
這將重複測試表達式,如果表示式為 true,則執行第一個套件;如果表示式為 false(這可能是第一次測試時),則執行 else
子句(如果存在)的套件,並且迴圈終止。
在第一個套件中執行的 break
語句會終止迴圈,而不執行 else
子句的套件。在第一個套件中執行的 continue
語句會跳過套件的其餘部分,然後返回測試表達式。
8.3. for
語句¶
for
語句用於迭代序列(例如字串、元組或列表)或其他可迭代物件的元素。
for_stmt ::= "for"target_list
"in"starred_list
":"suite
["else" ":"suite
]
starred_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 語句 部分。
8.4.1. except
子句¶
except
子句指定一個或多個異常處理程式。當 try
子句中沒有發生異常時,不執行任何異常處理程式。當 try
套件中發生異常時,將開始搜尋異常處理程式。此搜尋會依次檢查 except
子句,直到找到一個與異常匹配的子句為止。如果沒有表示式的 except
子句(如果存在),則必須放在最後;它會匹配任何異常。
對於帶有表示式的 except
子句,該表示式的求值結果必須是異常型別或異常型別的元組。引發的異常與 except
子句匹配,該子句的表示式求值結果為異常物件的類或該異常物件的非虛基類,或求值結果為包含此類別的元組。
如果沒有 except
子句與異常匹配,則會在周圍的程式碼和呼叫堆疊中繼續搜尋異常處理程式。[1]
如果 except
子句的頭部表示式求值時引發了異常,則會取消對處理程式的原始搜尋,並開始在周圍的程式碼和呼叫堆疊中搜索新的異常(就好像整個 try
語句引發了該異常一樣)。
當找到匹配的 except
子句時,如果存在 as
關鍵字,則將異常分配給該 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
模組中,可以透過在 except
子句的主體中呼叫 sys.exception()
來訪問它。當離開異常處理程式時,儲存在 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*
子句用於處理 ExceptionGroup
。匹配的異常型別解釋方式與 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 "<stdin>", line 2, in <module>
| ExceptionGroup: eg
+-+---------------- 1 ----------------
| ValueError: 1
+------------------------------------
任何未被任何 except*
子句處理的剩餘異常,以及從 except*
子句內引發的所有異常,都將在最後重新引發。如果此列表包含多個要重新引發的異常,它們將被組合成一個異常組。
如果引發的異常不是異常組,並且其型別與其中一個 except*
子句匹配,則會捕獲該異常,並將其包裝成一個帶有空訊息字串的異常組。
>>> try:
... raise BlockingIOError
... except* BlockingIOError as e:
... print(repr(e))
...
ExceptionGroup('', (BlockingIOError()))
except*
子句必須具有匹配的表示式;它不能是 except*:
。此外,此表示式不能包含異常組型別,因為這將導致語義不明確。
在同一個 try
中不能混合使用 except
和 except*
。break
、continue
和 return
不能出現在 except*
子句中。
8.4.3. else
子句¶
如果控制流離開 try
程式碼塊,沒有引發異常,並且沒有執行 return
、continue
或 break
語句,則會執行可選的 else
子句。else
子句中的異常不會由前面的 except
子句處理。
8.4.4. finally
子句¶
如果存在 finally
,則它指定一個“清理”處理程式。執行 try
子句,包括任何 except
和 else
子句。如果任何子句中發生異常且未被處理,則該異常會臨時儲存。執行 finally
子句。如果存在已儲存的異常,則會在 finally
子句結束時重新引發該異常。如果 finally
子句引發了另一個異常,則將儲存的異常設定為新異常的上下文。如果 finally
子句執行 return
、break
或 continue
語句,則會丟棄儲存的異常。
>>> def f():
... try:
... 1/0
... finally:
... return 42
...
>>> f()
42
在執行 finally
子句期間,程式無法訪問異常資訊。
當在 try
…finally
語句的 try
程式碼塊中執行 return
、break
或 continue
語句時,finally
子句也會在“退出時”執行。
函式的返回值由執行的最後一個 return
語句確定。由於 finally
子句始終執行,因此在 finally
子句中執行的 return
語句將始終是最後一個執行的語句。
>>> def foo():
... try:
... return 'try'
... finally:
... return 'finally'
...
>>> foo()
'finally'
在 3.8 版本中變更: 在 Python 3.8 之前,由於實現問題,continue
語句在 finally
子句中是不合法的。
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 語句之後使用。注意
在模式匹配失敗期間,某些子模式可能會成功。請不要依賴於為失敗的匹配所做的繫結。相反,請不要依賴於在失敗的匹配之後變數保持不變。確切的行為取決於實現,並且可能有所不同。這是為了允許不同的實現新增最佳化而做出的有意決定。
如果模式成功,則計算相應的保護(如果存在)。在這種情況下,保證發生所有名稱繫結。
如果保護評估為 true 或缺失,則執行
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
條件評估為 true,則選擇 case 塊。如果
guard
條件評估為 false,則不選擇 case 塊。如果
guard
在評估期間引發異常,則該異常會向上冒泡。
允許保護具有副作用,因為它們是表示式。保護評估必須從第一個 case 塊開始,逐個進行到最後一個 case 塊,跳過其模式並非全部成功的 case 塊。(即,保護評估必須按順序進行。)一旦選擇了 case 塊,保護評估就必須停止。
8.6.3. 不可反駁的 Case 塊¶
不可反駁的 case 塊是匹配所有 case 塊。一個 match 語句最多可以有一個不可反駁的 case 塊,並且它必須是最後一個。
如果 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 = <目標值>
。
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-字串。
形式 signed_number '+' NUMBER
和 signed_number '-' NUMBER
用於表示複數;它們要求左邊是實數,右邊是虛數。例如: 3 + 4j
。
簡單來說,LITERAL
僅在 <目標值> == LITERAL
時才會成功。對於單例 None
、True
和 False
,將使用 is
運算子。
8.6.4.4. 捕獲模式¶
捕獲模式將目標值繫結到一個名稱。 語法
capture_pattern ::= !'_' NAME
單個下劃線 _
不是捕獲模式(這就是 !'_'
所表達的)。而是將其視為萬用字元模式
。
在給定的模式中,一個給定的名稱只能繫結一次。例如:case x, x: ...
無效,而 case [x] | x: ...
允許。
捕獲模式始終成功。繫結遵循 PEP 572 中賦值表示式運算子建立的作用域規則;該名稱將成為最近的包含函式作用域中的區域性變數,除非有適用的 global
或 nonlocal
語句。
簡單來說,NAME
始終會成功,並且會設定 NAME = <目標值>
。
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
僅在 <目標值> == NAME1.NAME2
時才會成功
注意
如果同一個值在同一匹配語句中出現多次,直譯器可能會快取找到的第一個值並重復使用它,而不是重複相同的查詢。此快取嚴格繫結到給定匹配語句的給定執行。
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
)
對於序列模式,使用括號還是方括號沒有區別(即 (...)
與 [...]
)。
注意
用括號括起來的沒有尾隨逗號的單個模式(例如:(3 | 4)
)是一個分組模式。而用方括號括起來的單個模式(例如:[3 | 4]
)仍然是一個序列模式。
一個序列模式中最多可以有一個星號子模式。星號子模式可以出現在任何位置。如果不存在星號子模式,則該序列模式是固定長度的序列模式;否則,它是可變長度的序列模式。
以下是將序列模式與目標值匹配的邏輯流程
如果目標值不是[2]序列,則序列模式失敗。
如果目標值是
str
、bytes
或bytearray
的例項,則序列模式失敗。後續步驟取決於序列模式是固定長度還是可變長度。
如果序列模式是固定長度的
如果目標序列的長度不等於子模式的數量,則序列模式失敗
序列模式中的子模式從左到右與其在目標序列中的對應項進行匹配。一旦子模式失敗,匹配就會停止。如果所有子模式都成功匹配了對應的項,則序列模式成功。
否則,如果序列模式是可變長度的
如果目標序列的長度小於非星號子模式的數量,則序列模式失敗。
前導的非星號子模式與固定長度序列的對應項進行匹配。
如果上一步成功,則星號子模式匹配由剩餘的目標項形成的列表,不包括星號子模式之後剩餘的與非星號子模式對應的項。
其餘的非星號子模式與它們對應的目標項進行匹配,就像固定長度序列一樣。
簡單來說, [P1, P2, P3,
… , P<N>]
僅在以下所有情況發生時才匹配
檢查
<目標值>
是否是序列len(目標值) == <N>
P1
匹配<目標值>[0]
(請注意,此匹配也可以繫結名稱)P2
匹配<目標值>[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
[","]]] | "**"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 中的新功能。
當一個或多個形參具有 *形參* =
*表示式* 的形式時,該函式被稱為具有“預設形參值”。 對於具有預設值的形參,在呼叫時可以省略相應的實參,在這種情況下,將替換為該形參的預設值。 如果一個形參具有預設值,那麼直到 “*
” 之前的所有後續形參也必須具有預設值——這是一個語法限制,而沒有在語法中表達出來。
預設形參值在函式定義執行時從左到右進行求值。 這意味著表示式在定義函式時求值一次,並且每次呼叫都使用相同的“預計算”值。 當預設形參值是可變物件(例如列表或字典)時,這一點尤其重要:如果函式修改了該物件(例如,透過將專案追加到列表),則實際上會修改預設形參值。 這通常不是預期的結果。 一種解決方法是使用 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瞭解詳細資訊。
形參可以具有 “: 表示式
” 形式的註解,該註解緊隨形參名稱之後。 任何形參都可以具有註解,甚至包括 *identifier
或 **identifier
形式的形參。(作為特例,*identifier
形式的形參可以具有 “: *表示式
” 的註解。) 函式可以在形參列表之後具有 “-> 表示式
” 形式的“返回”註解。 這些註解可以是任何有效的 Python 表示式。 註解的存在不會改變函式的語義。 註解值在函式物件的 __annotations__
屬性中作為鍵入形參名稱的字典的值提供。 如果使用了來自 __future__
的 annotations
匯入,則會在執行時將註解保留為字串,從而實現延遲求值。 否則,它們會在執行函式定義時求值。 在這種情況下,註解的求值順序可能與它們在原始碼中出現的順序不同。
3.11 版本更改: *identifier
形式的形參可以具有 “: *表示式
” 的註解。 請參閱PEP 646。
還可以建立匿名函式(未繫結到名稱的函式),以便在表示式中立即使用。 這使用 lambda 表示式,在 Lambdas 一節中進行了描述。 請注意,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
,邊界和約束不會在建立泛型物件時進行評估,而僅在透過屬性 __bound__
和 __constraints__
顯式訪問值時進行評估。為此,邊界或約束會在單獨的註解作用域中進行評估。
typing.TypeVarTuple
和 typing.ParamSpec
不能有邊界或約束。
所有三種類型的型別引數也可以具有一個預設值,當未顯式提供型別引數時使用該預設值。這是透過附加一個等號 (=
) 後跟一個表示式來新增的。與型別變數的邊界和約束一樣,預設值不會在建立物件時進行評估,而僅在訪問型別引數的 __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
之類的大寫字母開頭的名稱實際上在執行時不會繫結。
腳註