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

(關於 attributerefsubscription 的語法定義,請參閱 Primaries 章節。)

賦值陳述式會對運算式串列進行求值(請記住,這可以是單個運算式或以逗號分隔的串列,後者會產生一個元組),並將單個結果物件從左到右賦值給每個目標串列。

賦值是根據目標(串列)的形式遞迴定義的。當目標是可變物件的一部分(屬性參照或下標)時,該可變物件最終必須執行賦值並決定其有效性,如果賦值不被接受則可能會引發例外。各種型別遵循的規則以及引發的例外在物件型別的定義中給定(請參閱標準型別階層章節)。

將物件賦值給目標串列(可選擇性地用圓括號或方括號括起來)是以下列方式遞迴定義的。

  • 如果目標串列是單個目標且沒有尾隨逗號(可選擇性地用圓括號括起來),則物件會被賦值給該目標。

  • 否則:

    • 如果目標串列包含一個以星號為前綴的目標,稱為 "starred" 目標:該物件必須是一個可疊代物件,其項目數量至少與目標串列中的目標數量減一一樣多。可疊代物件的前幾個項目從左到右賦值給 starred 目標之前的目標。可疊代物件的最後幾個項目賦值給 starred 目標之後的目標。然後可疊代物件中剩餘項目的串列會被賦值給 starred 目標(該串列可以為空)。

    • 否則:該物件必須是一個可疊代物件,其項目數量與目標串列中的目標數量相同,並且這些項目從左到右賦值給對應的目標。

將物件賦值給單個目標是以下列方式遞迴定義的。

  • 如果目標是一個識別字(名稱):

    • 如果該名稱沒有出現在當前程式碼區塊的 globalnonlocal 陳述式中:該名稱會被繫結到當前區域命名空間中的物件。

    • 否則:該名稱分別被繫結到全域命名空間或由 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: augtarget augop (expression_list | yield_expression)
augtarget:                 identifier | attributeref | subscription
augop:                     "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**="
                           | ">>=" | "<<=" | "&=" | "^=" | "|="

(關於最後三個符號的語法定義,請參閱 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__() 呼叫。

也參考

PEP 526 - 變數註釋的語法

此提案新增了用於註釋變數型別(包括類別變數和實例變數)的語法,取代透過註解來表達的方式。

PEP 484 - 型別提示

新增了 typing 模組的提案,提供了可用於靜態分析工具和 IDE 的標準型別註釋語法。

在 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 陳述式