簡體   English   中英

Python 如何在遇到聲明之前知道有一個局部變量?

[英]How does Python know there is a local variable before encountering its declaration?

def f(): 
    print("Before", locals())   # line 2
    print(x);                   # line 3
    x = 2                       # line 4
    print("After", locals())    # line 5

x = 1
f()

我知道 Python 中的 LEGB 范圍規則。

對於上面的代碼,當我注釋掉第 4 行時,一切都按預期正常執行:對於第 3 行,python 沒有在本地范圍內找到變量x ,因此在找到它的全局范圍內搜索它並打印 1。

但是,當我在沒有注釋的情況下執行整個代碼時,它會引發UnboundLocalError: local variable 'x' referenced before assignment

我知道我可以使用非本地和全局,但我的問題是:

  1. python如何在遇到局部變量聲明之前知道有一個局部變量聲明?
  2. 即使它確實知道在本地范圍內有一個名為 x 的變量(盡管尚未初始化),為什么它不在 locals() 中顯示它?

我嘗試在類似的問題建議中找到答案,但失敗了。 如果我的任何理解有誤,請更正。

在某種程度上,答案是特定於實現的,因為 Python 只指定了預期的行為,而不是如何實現它。

也就是說,讓我們看看通常的實現 CPython 為f生成的字節碼:

>>> import dis
>>> dis.dis(f)
  2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('Before')
              4 LOAD_GLOBAL              1 (locals)
              6 CALL_FUNCTION            0
              8 CALL_FUNCTION            2
             10 POP_TOP

  3          12 LOAD_GLOBAL              0 (print)
             14 LOAD_FAST                0 (x)
             16 CALL_FUNCTION            1
             18 POP_TOP

  4          20 LOAD_CONST               2 (2)
             22 STORE_FAST               0 (x)

  5          24 LOAD_GLOBAL              0 (print)
             26 LOAD_CONST               3 ('After')
             28 LOAD_GLOBAL              1 (locals)
             30 CALL_FUNCTION            0
             32 CALL_FUNCTION            2
             34 POP_TOP
             36 LOAD_CONST               0 (None)
             38 RETURN_VALUE

有幾種不同的LOAD_*操作碼用於檢索各種值。 LOAD_GLOBAL用於全局范圍內的名稱; LOAD_CONST用於未分配給任何名稱的本地值。 LOAD_FAST用於局部變量。 局部變量甚至不按名稱存在,而是按數組中的索引存在。 這就是它們“快”的原因; 它們以數組而不是哈希表的形式提供。 LOAD_GLOBAL也使用整數參數,但這只是名稱數組的索引;名稱本身仍然需要在提供全局范圍的任何映射中查找。)

您甚至可以看到與f關聯的常量和局部值:

>>> f.__code__.co_consts
(None, 'Before', 2, 'After')
>>> f.__code__.co_varnames
('x',)

LOAD_CONST 1Before放入堆棧,因為f.__code__.co_consts[1] == 'Before' ,而LOAD_FAST 0x的值放入堆棧,因為f.__code__.co_varnames[0] == 'x'

這里的關鍵是字節碼是在f執行之前生成的。 Python 不只是在第一次看到它時執行每一行。 執行def語句包括:

  1. 閱讀源代碼
  2. 解析為抽象語法樹 (AST)
  3. 使用整個AST 生成存儲在函數對象的__code__屬性中的字節碼。

代碼生成的一部分注意到名稱x ,由於在函數體中某處的賦值(即使該函數在邏輯上不可訪問),是一個本地名稱,因此必須使用LOAD_FAST訪問。

在調用locals時(實際上是在第一次使用LOAD_FAST 0之前),尚未對x進行分配(即STORE_FAST 0 ),因此插槽 0沒有要查找的本地值。

因為你在f()函數調用之前定義了它,

讓我們試試這個:

def f(y):
    print("Before", locals())   # line 2
    print(y);                   # line 3
    y = 2                       # line 4
    print("After", locals())    # line 5

f(x)
x = 1

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM