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: 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" 陳述式
====================

   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:     ["lazy"] "import" module ["as" identifier] ("," module ["as" identifier])*
                    | ["lazy"] "from" relative_module "import" identifier ["as" identifier]
                    ("," identifier ["as" identifier])*
                    | ["lazy"] "from" relative_module "import" "(" identifier ["as" identifier]
                    ("," identifier ["as" identifier])* [","] ")"
                    | "from" relative_module "import" "*"
   module:          (identifier ".")* identifier
   relative_module: "."* module | "."+

基本的 import 陳述式（不含 "from" 子句）分兩個步驟執行：

1. 找到一個模組，如有必要則載入並初始化它

2. define a name or names in the current namespace for the scope where
   the "import" statement occurs, just as an assignment statement
   would (including "global" and "nonlocal" semantics).

當陳述式包含多個子句（以逗號分隔）時，這兩個步驟會分別對每個子句執行，
就像這些子句被分離成單獨的 import 陳述式一樣。

第一步（尋找和載入模組）的詳細資訊在引入系統章節中有更詳細的描述，該章
節還描述了可以引入的各種套件和模組型別，以及所有可用於自訂引入系統的掛
鉤 (hook)。請注意，此步驟的失敗可能表示找不到模組，*或者*在初始化模組
時發生錯誤，這包括執行模組的程式碼。

如果請求的模組被成功取得，它將透過以下三種方式之一在區域命名空間中可用
：

* 如果模組名稱後面跟著 "as"，則 "as" 後面的名稱會直接繫結到被引入的模
  組。

* 如果沒有指定其他名稱，且被引入的模組是頂層模組，則模組的名稱會在區域
  命名空間中繫結為被引入模組的參照

* 如果被引入的模組*不是*頂層模組，則包含該模組的頂層套件的名稱會在區域
  命名空間中繫結為頂層套件的參照。被引入的模組必須使用其完整的限定名稱
  來存取，而不是直接存取

"from" 形式使用稍微複雜一點的過程：

1. 找到 "from" 子句中指定的模組，如有必要則載入並初始化它；

2. 對於 "import" 子句中指定的每個識別字：

   1. 檢查被引入的模組是否有該名稱的屬性

   2. 如果沒有，嘗試引入具有該名稱的子模組，然後再次檢查被引入的模組是
      否有該屬性

   3. 如果找不到該屬性，則會引發 "ImportError"。

   4. otherwise, a reference to that value is stored in the current
      namespace, using the name in the "as" clause if it is present,
      otherwise using the attribute name

範例：

   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. Lazy imports
--------------------

The "lazy" keyword is a soft keyword that only has special meaning
when it appears immediately before an "import" or "from" statement.
When an import statement is preceded by the "lazy" keyword, the import
becomes *lazy*: the module is not loaded immediately at the import
statement. Instead, a lazy proxy object is created and bound to the
name. The actual module is loaded on first use of that name.

Lazy imports are only permitted at module scope. Using "lazy" inside a
function, class body, or "try"/"except"/"finally" block raises a
"SyntaxError". Star imports cannot be lazy ("lazy from module import
*" is a syntax error), and future statements cannot be lazy.

When using "lazy from ... import", each imported name is bound to a
lazy proxy object. The first access to any of these names triggers
loading of the entire module and resolves only that specific name to
its actual value. Other names remain as lazy proxies until they are
accessed.

Example:

   lazy import json
   import sys

   print('json' in sys.modules)  # False - json module not yet loaded

   # First use triggers loading
   result = json.dumps({"hello": "world"})

   print('json' in sys.modules)  # True - now loaded

If an error occurs during module loading (such as "ImportError" or
"SyntaxError"), it is raised at the point where the lazy import is
first used, not at the import statement itself.

See **PEP 810** for the full specification of lazy imports.

在 3.15 版被加入.


7.11.1.1. Compatibility via "__lazy_modules__"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

As an alternative to using the "lazy" keyword, a module can opt into
lazy loading for specific imports by defining a module-level
"__lazy_modules__" variable.  When present, it must be a container of
fully qualified module name strings.  Any regular (non-"lazy")
"import" statement at module scope whose target appears in
"__lazy_modules__" is treated as a lazy import, exactly as if the
"lazy" keyword had been used.

This provides a way to enable lazy loading for specific dependencies
without changing individual "import" statements. This is useful when
supporting Python versions older than 3.15 while using lazy imports in
3.15+:

   __lazy_modules__ = ["json", "pathlib"]

   import json     # loaded lazily (name is in __lazy_modules__)
   import os       # loaded eagerly (name not in __lazy_modules__)

   import pathlib  # loaded lazily

Relative imports are resolved to their absolute name before the
lookup, so "__lazy_modules__" must always contain fully qualified
module names.

For "from"-style imports, the relevant name is the module following
"from", not the names of its members:

   # In mypackage/mymodule.py
   __lazy_modules__ = ["mypackage", "mypackage.sub.utils"]

   from . import helper         # loaded lazily: . resolves to mypackage
   from .sub.utils import func  # loaded lazily: .sub.utils resolves to mypackage.sub.utils
   import json                  # loaded eagerly (not in __lazy_modules__)

Imports inside functions, class bodies, or "try"/"except"/"finally"
blocks are always eager, regardless of "__lazy_modules__".

Setting "-X lazy_imports=none" (or the "PYTHON_LAZY_IMPORTS"
environment variable to "none") overrides "__lazy_modules__" and
forces all imports to be eager.

在 3.15 版被加入.


7.11.2. 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"。

也參考:

  **PEP 3104** - 存取外層作用域中的名稱
     "nonlocal" 陳述式的規格說明。

**程式設計師注意事項：**"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" 陳述式以及泛型類別和函式的語法。
