7. 簡單陳述式¶
一個簡單陳述式會被包含在一個單獨的邏輯列中。多個簡單陳述式用分號分隔就可以出現在同一列中。簡單陳述式的語法如下:
simple_stmt:expression_stmt|assert_stmt|assignment_stmt|augmented_assignment_stmt|annotated_assignment_stmt|pass_stmt|del_stmt|return_stmt|yield_stmt|raise_stmt|break_stmt|continue_stmt|import_stmt|future_stmt|global_stmt|nonlocal_stmt|type_stmt
7.1. 運算式陳述式¶
運算式陳述式(主要在互動模式下)用於計算並寫入一個值,或者(通常)用於呼叫一個程序 (procedure)(一個不回傳有意義結果的函式;在 Python 中,程序會回傳 None 值)。運算式陳述式的其他用途也是被允許的,並且偶爾會很有用。運算式陳述式的語法如下:
expression_stmt: starred_expression
運算式陳述式會對運算式串列(可能是單個運算式)進行求值 (evaluate)。
在互動模式下,如果值不是 None,就會以內建的 repr() 函式轉換成字串,並將結果字串單獨寫入標準輸出的一列中(除非結果是 None,則程序呼叫就不會產生任何輸出)。
7.2. 賦值陳述式¶
賦值陳述式用於將名稱(重新)繫結到值,以及修改可變物件的屬性或項目:
assignment_stmt: (target_list"=")+ (starred_expression|yield_expression) target_list:target(","target)* [","] target:identifier| "(" [target_list] ")" | "[" [target_list] "]" |attributeref|subscription| "*"target
(關於 attributeref 和 subscription 的語法定義,請參閱 Primaries 章節。)
賦值陳述式會對運算式串列進行求值(請記住,這可以是單個運算式或以逗號分隔的串列,後者會產生一個元組),並將單個結果物件從左到右賦值給每個目標串列。
賦值是根據目標(串列)的形式遞迴定義的。當目標是可變物件的一部分(屬性參照或下標)時,該可變物件最終必須執行賦值並決定其有效性,如果賦值不被接受則可能會引發例外。各種型別遵循的規則以及引發的例外在物件型別的定義中給定(請參閱標準型別階層章節)。
將物件賦值給目標串列(可選擇性地用圓括號或方括號括起來)是以下列方式遞迴定義的。
如果目標串列是單個目標且沒有尾隨逗號(可選擇性地用圓括號括起來),則物件會被賦值給該目標。
否則:
如果目標串列包含一個以星號為前綴的目標,稱為 "starred" 目標:該物件必須是一個可疊代物件,其項目數量至少與目標串列中的目標數量減一一樣多。可疊代物件的前幾個項目從左到右賦值給 starred 目標之前的目標。可疊代物件的最後幾個項目賦值給 starred 目標之後的目標。然後可疊代物件中剩餘項目的串列會被賦值給 starred 目標(該串列可以為空)。
否則:該物件必須是一個可疊代物件,其項目數量與目標串列中的目標數量相同,並且這些項目從左到右賦值給對應的目標。
將物件賦值給單個目標是以下列方式遞迴定義的。
如果目標是一個識別字(名稱):
如果該名稱沒有出現在當前程式碼區塊的
global或nonlocal陳述式中:該名稱會被繫結到當前區域命名空間中的物件。否則:該名稱分別被繫結到全域命名空間或由
nonlocal決定的外層命名空間中的物件。
如果名稱已經被繫結,則會重新繫結。這可能導致先前繫結到該名稱的物件的參照計數變為零,從而導致該物件被釋放並呼叫其解構函式(如果有的話)。
如果目標是屬性參照:參照中的主要運算式會被求值。它應該產生一個具有可賦值屬性的物件;如果不是這種情況,則會引發
TypeError。然後要求該物件將被賦值的物件賦值給指定的屬性;如果它無法執行賦值,則會引發例外(通常是AttributeError但並非一定)。注意:如果物件是類別實例,且屬性參照出現在賦值運算子的兩側,右側運算式
a.x可以存取實例屬性或(如果不存在實例屬性)類別屬性。左側目標a.x總是被設定為實例屬性,如有必要會建立它。因此,a.x的兩次出現不一定指向同一個屬性:如果右側運算式參照的是類別屬性,左側會建立一個新的實例屬性作為賦值的目標:class Cls: x = 3 # 類別變數 inst = Cls() inst.x = inst.x + 1 # 將 inst.x 寫為 4,Cls.x 保持為 3
此描述不一定適用於描述器屬性,例如使用
property()建立的特性 (property)。如果目標是下標:參照中的主要運算式會被求值。接著,下標運算式會被求值。然後會以兩個引數(下標和被賦值的物件)呼叫主要物件的
__setitem__()方法。通常
__setitem__()被定義在可變序列物件(如串列)和對映物件(如字典)上,其行為如下。如果主要物件是可變序列物件(如串列),下標必須產生一個整數。如果它是負數,會加上序列的長度。結果值必須是一個非負整數且小於序列的長度,然後要求該序列將被賦值的物件賦值給具有該索引的項目。如果索引超出範圍,則會引發
IndexError(對下標序列的賦值無法向串列新增項目)。如果主要物件是對映物件(如字典),下標的型別必須與對映的鍵型別相容,然後要求該對映建立一個將下標對映到被賦值物件的鍵/值對。這可以替換具有相同鍵值的現有鍵/值對,或插入新的鍵/值對(如果不存在相同值的鍵)。
如果目標是切片:主要運算式應該求值為一個可變序列物件(如串列)。被賦值的物件應該是可疊代的。切片的下界和上界應該是整數;如果它們是
None(或不存在),預設值為零和序列的長度。如果任一邊界為負數,則會加上序列的長度。結果邊界會被裁剪到零和序列長度之間(包含邊界)。最後,要求該序列物件用被賦值序列的項目替換切片。切片的長度可能與被賦值序列的長度不同,因此會改變目標序列的長度(如果目標序列允許的話)。
雖然賦值的定義意味著左側和右側之間的重疊是「同時的」(例如 a, b = b, a 會交換兩個變數),但被賦值變數集合內部的重疊是從左到右發生的,有時會導致混淆。例如,以下程式會印出 [0, 2]:
x = [0, 1]
i = 0
i, x[i] = 1, 2 # i 先被更新,然後 x[i] 再被更新
print(x)
也參考
- PEP 3132 - 擴充可疊代物件拆解
*target功能的規格說明。
7.2.1. 擴增賦值陳述式¶
擴增賦值是將二元運算與賦值陳述式結合在單一陳述式中:
augmented_assignment_stmt:augtargetaugop(expression_list|yield_expression) augtarget:identifier|attributeref|subscriptionaugop: "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**=" | ">>=" | "<<=" | "&=" | "^=" | "|="
(關於最後三個符號的語法定義,請參閱 Primaries 章節。)
擴增賦值會對目標(與一般賦值陳述式不同,它不能是拆解運算)和運算式串列進行求值,對這兩個運算元執行該賦值型別特定的二元運算,並將結果賦值給原始目標。目標只會被求值一次。
像 x += 1 這樣的擴增賦值陳述式可以改寫為 x = x + 1 來達到類似但不完全相同的效果。在擴增版本中,x 只會被求值一次。此外,在可能的情況下,實際的運算會原地 (in-place)執行,意即不是建立一個新物件並將其賦值給目標,而是直接修改舊物件。
與一般賦值不同,擴增賦值會在求值右側之前先求值左側。例如,a[i] += f(x) 會先查找 a[i],然後求值 f(x) 並執行加法,最後將結果寫回 a[i]。
除了在單一陳述式中賦值給元組和多個目標的情況外,擴增賦值陳述式執行的賦值與一般賦值的處理方式相同。同樣地,除了可能的原地行為外,擴增賦值執行的二元運算與一般二元運算相同。
對於屬性參照的目標,與一般賦值相同的關於類別和實例屬性的注意事項也適用。
7.2.2. 註釋賦值陳述式¶
註釋 賦值是將變數或屬性註釋與可選的賦值陳述式結合在一個陳述式中:
annotated_assignment_stmt:augtarget":"expression["=" (starred_expression|yield_expression)]
與一般賦值的區別在於只允許單個目標。
如果賦值目標由一個未被圓括號括起來的單一名稱組成,則被視為「簡單的」。對於簡單的賦值目標,如果在類別或模組作用域中,註釋會被收集在一個惰性求值的註釋作用域中。註釋可以使用類別或模組的 __annotations__ 屬性來求值,或者使用 annotationlib 模組中的工具。
如果賦值目標不是簡單的(屬性、下標節點或被圓括號括起來的名稱),則註釋永遠不會被求值。
如果一個名稱在函式作用域中被註釋,則該名稱對該作用域而言是區域的。註釋永遠不會在函式作用域中被求值和儲存。
如果右側存在,註釋賦值會執行實際的賦值,就像沒有註釋一樣。如果運算式目標的右側不存在,則直譯器會對目標進行求值,但不包括最後的 __setitem__() 或 __setattr__() 呼叫。
也參考
在 3.8 版的變更: 現在註釋賦值允許右側與一般賦值相同的運算式。以前,某些運算式(如未加圓括號的元組運算式)會導致語法錯誤。
在 3.14 版的變更: 註釋現在會在獨立的註釋作用域中惰性求值。如果賦值目標不是簡單的,則註釋永遠不會被求值。
7.3. assert 陳述式¶
Assert 陳述式是將除錯斷言插入程式中的便捷方式:
assert_stmt: "assert"expression[","expression]
簡單形式 assert expression 等價於:
if __debug__:
if not expression: raise AssertionError
擴展形式 assert expression1, expression2 等價於:
if __debug__:
if not expression1: raise AssertionError(expression2)
這些等價關係假設 __debug__ 和 AssertionError 參照的是具有這些名稱的內建變數。在目前的實作中,內建變數 __debug__ 在正常情況下為 True,在請求最佳化時(命令列選項 -O)為 False。當在編譯時請求最佳化時,目前的程式碼產生器不會為 assert 陳述式產生任何程式碼。請注意,沒有必要在錯誤訊息中包含失敗運算式的原始碼;它會作為堆疊追蹤的一部分顯示。
對 __debug__ 進行賦值是非法的。內建變數的值在直譯器啟動時決定。
7.4. pass 陳述式¶
pass_stmt: "pass"
pass 是一個空操作——當它被執行時,什麼事都不會發生。當語法上需要一個陳述式但不需要執行任何程式碼時,它可以作為佔位符使用,例如:
def f(arg): pass # 一個(還)沒有實作的函式
class C: pass # 一個(還)沒有方法的類別
7.5. del 陳述式¶
del_stmt: "del" target_list
刪除的遞迴定義與賦值的定義方式非常相似。這裡提供一些提示,而不是詳細說明全部細節。
刪除目標串列會從左到右遞迴刪除每個目標。
刪除名稱會從區域或全域命名空間中移除該名稱的繫結,取決於該名稱是否出現在同一程式碼區塊的 global 陳述式中。嘗試刪除未繫結的名稱會引發 NameError 例外。
屬性參照和下標的刪除會傳遞給涉及的主要物件;切片的刪除通常等價於賦值一個正確型別的空切片(但這一點也是由被切片的物件決定的)。
在 3.2 版的變更: 以前,如果一個名稱作為自由變數出現在巢狀區塊中,則從區域命名空間刪除該名稱是非法的。
7.6. return 陳述式¶
return_stmt: "return" [expression_list]
return 只能在語法上出現在函式定義的巢狀結構中,不能出現在巢狀類別定義中。
如果運算式串列存在,則會對其求值,否則會以 None 替代。
return 會離開當前的函式呼叫,並以運算式串列(或 None)作為回傳值。
當 return 將控制權從帶有 finally 子句的 try 陳述式中傳出時,該 finally 子句會在真正離開函式之前被執行。
在產生器函式中,return 陳述式表示產生器已完成,並會導致引發 StopIteration。回傳值(如果有的話)會被用作建構 StopIteration 的引數,並成為 StopIteration.value 屬性。
在非同步產生器函式中,空的 return 陳述式表示非同步產生器已完成,並會導致引發 StopAsyncIteration。在非同步產生器函式中,非空的 return 陳述式是語法錯誤。
7.7. yield 陳述式¶
yield_stmt: yield_expression
yield 陳述式在語義上等價於yield 運算式。yield 陳述式可用於省略在等價的 yield 運算式陳述式中原本需要的圓括號。例如,以下 yield 陳述式:
yield <expr>
yield from <expr>
等價於以下 yield 運算式陳述式:
(yield <expr>)
(yield from <expr>)
Yield 運算式和陳述式只在定義產生器函式時使用,且只在產生器函式的主體中使用。在函式定義中使用 yield 就足以使該定義建立一個產生器函式而非一般函式。
關於 yield 語義的完整詳情,請參閱Yield expressions章節。
7.8. raise 陳述式¶
raise_stmt: "raise" [expression["from"expression]]
如果沒有運算式,raise 會重新引發當前正在處理的例外,這也稱為活動例外。如果當前沒有活動例外,則會引發 RuntimeError 例外,表示這是一個錯誤。
否則,raise 會將第一個運算式求值為例外物件。它必須是 BaseException 的子類別或實例。如果它是一個類別,則在需要時會透過不帶引數地實例化該類別來獲得例外實例。
例外的型別 (type)是例外實例的類別,值 (value)是實例本身。
通常在引發例外時會自動建立一個追蹤物件,並將其作為 __traceback__ 屬性附加到例外上。你可以使用 with_traceback() 例外方法(它會回傳同一個例外實例,並將其追蹤設定為其引數)在一個步驟中建立例外並設定你自己的追蹤,如下所示:
raise Exception("foo occurred").with_traceback(tracebackobj)
from 子句用於例外鏈結:如果給定,第二個運算式必須是另一個例外類別或實例。如果第二個運算式是例外實例,它將作為 __cause__ 屬性(可寫入)附加到被引發的例外上。如果運算式是例外類別,則會實例化該類別,並將產生的例外實例作為 __cause__ 屬性附加到被引發的例外上。如果被引發的例外未被處理,則兩個例外都會被印出:
>>> try:
... print(1 / 0)
... except Exception as exc:
... raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
print(1 / 0)
~~^~~
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError("Something bad happened") from exc
RuntimeError: Something bad happened
如果在已經處理一個例外時引發了新的例外,類似的機制會隱式地運作。使用 except 或 finally 子句,或 with 陳述式時,例外可能正在被處理。然後先前的例外會作為新例外的 __context__ 屬性附加:
>>> try:
... print(1 / 0)
... except:
... raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
print(1 / 0)
~~^~~
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError("Something bad happened")
RuntimeError: Something bad happened
可以透過在 from 子句中指定 None 來明確抑制例外鏈結:
>>> try:
... print(1 / 0)
... except:
... raise RuntimeError("Something bad happened") from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened
關於例外的更多資訊可以在例外章節找到,關於處理例外的資訊可以在try 陳述式章節找到。
在 3.3 版的變更: None 現在允許作為 raise X from Y 中的 Y。
新增了 __suppress_context__ 屬性以抑制例外情境的自動顯示。
在 3.11 版的變更: 如果活動例外的追蹤在 except 子句中被修改,則後續的 raise 陳述式會使用修改後的追蹤重新引發例外。以前,例外會使用它被捕獲時的追蹤重新引發。
7.9. break 陳述式¶
break_stmt: "break"
break 只能在語法上出現在 for 或 while 迴圈的巢狀結構中,但不能巢狀在該迴圈內的函式或類別定義中。
它會終止最近的外層迴圈,如果迴圈有可選的 else 子句,則會跳過它。
如果 for 迴圈被 break 終止,迴圈控制目標會保持其當前值。
當 break 將控制權從帶有 finally 子句的 try 陳述式中傳出時,該 finally 子句會在真正離開迴圈之前被執行。
7.10. continue 陳述式¶
continue_stmt: "continue"
continue 在語法上只能出現於 for 或 while 迴圈的巢狀結構中,但不能在該迴圈內的函式或類別定義中。它會繼續執行最近外層迴圈的下一個循環。
當 continue 將控制權從帶有 finally 子句的 try 陳述式中傳出時,該 finally 子句會在真正開始下一個迴圈循環之前被執行。
7.11. import 陳述式¶
import_stmt: "import"module["as"identifier] (","module["as"identifier])* | "from"relative_module"import"identifier["as"identifier] (","identifier["as"identifier])* | "from"relative_module"import" "("identifier["as"identifier] (","identifier["as"identifier])* [","] ")" | "from"relative_module"import" "*" module: (identifier".")*