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 版的變更: 以前,如果一個名稱作為自由變數出現在巢狀區塊中,則從區域命名空間刪除該名稱是非法的。