11

我查看了SO上的無數'Python exec'線程,但找不到解決我的問題的線程。非常抱歉,如果這之前已經問過。這裏是我的問題:爲什麼Python 3更改爲exec會中斷此代碼?

# Python 2.6: prints 'it is working' 
# Python 3.1.2: "NameError: global name 'a_func' is not defined" 
class Testing(object): 
    def __init__(self): 
    exec("""def a_func(): 
     print('it is working')""") 
    a_func() 

Testing() 

# Python 2.6: prints 'it is working' 
# Python 3.1.2: prints 'it is working' 
class Testing(object): 
    def __init__(self): 
    def a_func(): 
     print('it is working') 
    a_func() 

Testing() 

作爲標準功能定義在兩個Python版本的作品,我假設的問題必須是一個變化的方式EXEC作品。我讀了API文檔的2.6和3 exec和也可以參考「什麼是新的Python 3.0」頁面,看不出有任何理由代碼將打破。

+5

這似乎是很可能使任何人必須在5年內維持它的代碼。 – Amber

+4

我認爲這是一個比實際使用的代碼更包含的例子。我聽說'exec'和'eval'在語言中佔有一席之地。 –

+3

@Amber也許我是個虐待狂? –

回答

10

你可以看到生成的字節碼爲每個Python版本有:

>>> from dis import dis 

,併爲每個解釋:

#Python 3.2 
>>> dis(Testing.__init__) 
... 
    5   10 LOAD_GLOBAL    1 (a_func) 
... 

#Python 2.7 
>>> dis(Testing.__init__) 
... 
    5   8 LOAD_NAME    0 (a_func) 
... 

正如你所看到的,Python的3.2搜索一個全局值(LOAD_GLOBAL )名爲a_func,2.7在搜索全局範圍之前首先搜索本地範圍(LOAD_NAME)。

如果您在exec之後做print(locals()),您會看到在__init__函數內部創建了a_func

我真的不知道爲什麼它這樣做的方式,但似乎對symbol tables是如何處理的變化。

順便說一句,如果想在你的__init__方法做出解釋知道這是一個局部變量的頂部創建一個a_func = None,它會不工作,因爲字節碼現在將LOAD_FAST和不進行搜索,而是直接從列表中獲取值。

我看到的唯一的解決辦法是作爲第二個參數添加到globals()exec,這樣會造成a_func作爲一個全球性的功能可以由LOAD_GLOBAL碼來訪問。

編輯

如果刪除exec聲明,Python2.7改變字節碼從LOAD_NAMELOAD_GLOBAL。因此,使用exec,您的代碼在Python2.x上總是比較慢,因爲它必須搜索本地作用域以進行更改。

由於Python3的exec不是關鍵字,因此解釋器無法確定它是真的執行新代碼還是執行其他操作......所以字節碼不會改變。

E.g.

>>> exec = len 
>>> exec([1,2,3]) 
3 

TL;博士

exec('...', globals())可以解決這個問題,如果你不小心被加入到全局命名空間

+0

它的工作。很好的答案;字節碼內省真的很酷。不知道這一點。感謝分享。 –

+0

@Jedidiah Hurt添加了更多信息。 – JBernardo

+1

由於'exec'正在考慮本地作用域,並且作爲'locals()'存儲它,所以還可以執行exec(「a_func()」)或locals()['a_func']()'。 – MGwynne

5

結果完成上述答案,以防萬一。如果exec是在一些功能,我會建議使用三參數版本如下:

def f(): 
    d = {} 
    exec("def myfunc(): ...", globals(), d) 
    d["myfunc"]() 

這是乾淨的解決方案,因爲它不會改變你腳下的任何命名空間。相反,myfunc存儲在顯式詞典d中。

+0

我喜歡這樣,感覺對我更安全:) – chisaipete

相關問題