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".")*identifierrelative_module: "."*module| "."+
基本的 import 陳述式(不含 from 子句)分兩個步驟執行:
找到一個模組,如有必要則載入並初始化它
在
import陳述式出現的作用域的區域命名空間中定義一個或多個名稱。
當陳述式包含多個子句(以逗號分隔)時,這兩個步驟會分別對每個子句執行,就像這些子句被分離成單獨的 import 陳述式一樣。
第一步(尋找和載入模組)的詳細資訊在引入系統章節中有更詳細的描述,該章節還描述了可以引入的各種套件和模組型別,以及所有可用於自訂引入系統的掛鉤 (hook)。請注意,此步驟的失敗可能表示找不到模組,或者在初始化模組時發生錯誤,這包括執行模組的程式碼。
如果請求的模組被成功取得,它將透過以下三種方式之一在區域命名空間中可用:
如果模組名稱後面跟著
as,則as後面的名稱會直接繫結到被引入的模組。如果沒有指定其他名稱,且被引入的模組是頂層模組,則模組的名稱會在區域命名空間中繫結為被引入模組的參照
如果被引入的模組不是頂層模組,則包含該模組的頂層套件的名稱會在區域命名空間中繫結為頂層套件的參照。被引入的模組必須使用其完整的限定名稱來存取,而不是直接存取
from 形式使用稍微複雜一點的過程:
找到
from子句中指定的模組,如有必要則載入並初始化它;對於
import子句中指定的每個識別字:檢查被引入的模組是否有該名稱的屬性
如果沒有,嘗試引入具有該名稱的子模組,然後再次檢查被引入的模組是否有該屬性
如果找不到該屬性,則會引發
ImportError。否則,對該值的參照會儲存在區域命名空間中,如果存在
as子句則使用其中的名稱,否則使用屬性名稱
範例:
import foo # foo 被引入並在本地繫結
import foo.bar.baz # foo、foo.bar 和 foo.bar.baz 被引入,foo 在本地繫結
import foo.bar.baz as fbb # foo、foo.bar 和 foo.bar.baz 被引入,foo.bar.baz 繫結為 fbb
from foo.bar import baz # foo、foo.bar 和 foo.bar.baz 被引入,foo.bar.baz 繫結為 baz
from foo import attr # foo 被引入,foo.attr 繫結為 attr
如果識別字串列被星號('*')取代,則模組中定義的所有公開名稱都會在 import 陳述式出現的作用域的區域命名空間中繫結。
模組定義的公開名稱是透過檢查模組命名空間中名為 __all__ 的變數來決定的;如果有定義,它必須是一個字串序列,這些字串是該模組定義或引入的名稱。包含非 ASCII 字元的名稱必須使用 normalization form NFKC;詳情請參閱 名稱中的非 ASCII 字元。__all__ 中給出的名稱都被視為公開的,且必須存在。如果 __all__ 沒有定義,公開名稱的集合包括模組命名空間中所有不以底線字元('_')開頭的名稱。__all__ 應該包含整個公開 API。這是為了避免意外匯出不屬於 API 的項目(例如在模組內被引入和使用的函式庫模組)。
萬用字元形式的引入——from module import *——只允許在模組層級使用。嘗試在類別或函式定義中使用它會引發 SyntaxError。
在指定要引入的模組時,你不必指定模組的絕對名稱。當一個模組或套件包含在另一個套件中時,可以在同一個頂層套件內進行相對引入,而不必提及套件名稱。透過在 from 後面的指定模組或套件中使用前導點,你可以指定要向上遍歷當前套件階層結構多高,而不必指定確切的名稱。一個前導點表示進行引入的模組所在的當前套件。兩個點表示向上一個套件層級。三個點表示向上兩個層級,依此類推。因此,如果你從 pkg 套件中的模組執行 from . import mod,你最終會引入 pkg.mod。如果你從 pkg.subpkg1 內部執行 from ..subpkg2 import mod,你會引入 pkg.subpkg2.mod。相對引入的規格說明包含在套件相對引入章節中。
importlib.import_module() 用於支援需要動態決定載入模組的應用程式。
引發一個附帶引數 module、filename、sys.path、sys.meta_path、sys.path_hooks 的稽核事件 import。
7.11.1. Future 陳述式¶
future 陳述式 (future statement)是給編譯器的指令,表示特定模組應該使用將在 Python 的指定未來版本(該功能成為標準的版本)中可用的語法或語義來編譯。
future 陳述式旨在簡化向 Python 未來版本的遷移,這些版本引入了語言的不相容變更。它允許在該功能成為標準的版本發佈之前,以每個模組為基礎使用新功能。
future_stmt: "from" "__future__" "import"feature["as"identifier] (","feature["as"identifier])* | "from" "__future__" "import" "("feature["as"identifier] (","feature["as"identifier])* [","] ")" feature:identifier
future 陳述式必須出現在模組的頂部附近。可以出現在 future 陳述式之前的行只有:
模組的文件字串(如果有的話),
註解,
空白行,以及
其他 future 陳述式。
唯一需要使用 future 陳述式的功能是 annotations(請參閱 PEP 563)。
所有透過 future 陳述式啟用的歷史功能仍被 Python 3 識別。這些功能包括 absolute_import、division、generators、generator_stop、unicode_literals、print_function、nested_scopes 和 with_statement。它們都是多餘的,因為它們總是被啟用的,只是為了向後相容性而保留。
future 陳述式在編譯時被識別並特別處理:對核心構造語義的變更通常透過產生不同的程式碼來實作。甚至可能出現新功能引入不相容語法(例如新的保留字)的情況,在這種情況下,編譯器可能需要以不同的方式剖析模組。這些決定無法延遲到 runtime 才做出。
對於任何給定的版本,編譯器知道哪些功能名稱已被定義,如果 future 陳述式包含它不認識的功能,則會引發編譯時錯誤。
直接的 runtime 語義與任何 import 陳述式相同:有一個標準模組 __future__(稍後描述),它會在 future 陳述式執行時以通常的方式被引入。
有趣的 runtime 語義取決於 future 陳述式啟用的特定功能。
請注意,以下陳述式沒有什麼特別的:
import __future__ [as name]
這不是 future 陳述式;它是一個普通的 import 陳述式,沒有特殊的語義或語法限制。
在包含 future 陳述式的模組 M 中,透過呼叫內建函式 exec() 和 compile() 編譯的程式碼預設會使用與 future 陳述式相關的新語法或語義。這可以透過 compile() 的可選引數來控制——詳情請參閱該函式的文件。
在互動式直譯器提示字元下輸入的 future 陳述式會在直譯器工作階段的其餘部分生效。如果直譯器使用 -i 選項啟動,傳入一個要執行的腳本名稱,且該腳本包含 future 陳述式,則它會在腳本執行後啟動的互動式工作階段中生效。
也參考
- PEP 236 - 回到 __future__
__future__ 機制的原始提案。
7.12. global 陳述式¶
global_stmt: "global"identifier(","identifier)*
global 陳述式會使列出的識別字被解釋為全域變數。如果沒有 global,就不可能對全域變數進行賦值,儘管自由變數可以在未被宣告為 global 的情況下參照全域變數。
global 陳述式適用於整個當前作用域(模組、函式主體或類別定義)。如果在該作用域中,變數在其 global 宣告之前被使用或賦值,則會引發 SyntaxError。
在模組層級,所有變數都是全域的,因此 global 陳述式沒有效果。然而,變數仍然不能在其 global 宣告之前被使用或賦值。這個要求在互動式提示符(REPL)中被放寬。
程式設計師注意事項:global 是給剖析器的指令。它只適用於與 global 陳述式同時被剖析的程式碼。特別是,包含在提供給內建 exec() 函式的字串或程式碼物件中的 global 陳述式不會影響包含該函式呼叫的程式碼區塊,而且這種字串中包含的程式碼不受包含函式呼叫的程式碼中的 global 陳述式影響。同樣的規則也適用於 eval() 和 compile() 函式。
7.13. nonlocal 陳述式¶
nonlocal_stmt: "nonlocal"identifier(","identifier)*
當函式或類別的定義巢狀(封閉)在其他函式的定義內時,它的非區域作用域就是外層函式的區域作用域。nonlocal 陳述式會使列出的識別字參照先前在非區域作用域中繫結的名稱。它允許被封裝的程式碼重新繫結這些非區域識別字。如果一個名稱在多個非區域作用域中被繫結,則使用最近的繫結。如果一個名稱在任何非區域作用域中都沒有被繫結,或者沒有非區域作用域,則會引發 SyntaxError。
nonlocal 陳述式適用於函式或類別主體的整個作用域。如果在該作用域中,變數在其 nonlocal 宣告之前被使用或賦值,則會引發 SyntaxError。
程式設計師注意事項:nonlocal 是給剖析器的指令,只適用於與它一起被剖析的程式碼。請參閱 global 陳述式的注意事項。
7.14. type 陳述式¶
type_stmt: 'type'identifier[type_params] "="expression
type 陳述式宣告一個型別別名,它是 typing.TypeAliasType 的實例。
例如以下陳述式建立了一個型別別名:
type Point = tuple[float, float]
這段程式碼大致等價於:
annotation-def VALUE_OF_Point():
return tuple[float, float]
Point = typing.TypeAliasType("Point", VALUE_OF_Point())
annotation-def 表示一個註釋作用域,它的行為大部分像函式,但有一些小差異。
型別別名的值在註釋作用域中被求值。它不是在型別別名建立時求值,而是只有在透過型別別名的 __value__ 屬性存取該值時才求值(請參閱Lazy evaluation)。這允許型別別名參照尚未定義的名稱。
型別別名可以透過在名稱後面新增型別參數串列來使其成為泛型。更多資訊請參閱Generic type aliases。
type 是一個軟關鍵字。
在 3.12 版被加入.
也參考
- PEP 695 - 型別參數語法
引入了
type陳述式以及泛型類別和函式的語法。