2017年9月6日 星期三

[ Python 常見問題 ] Python exec 出現 nested function with free variables 錯誤?

Source From Here 
Question 
考慮下面代碼: 
  1. def t(ab=233):  
  2.     def t2():print ab  
  3.     exec 'print ab'  
  4.   
  5. # SyntaxError: unqualified exec is not allowed in function 't'  
  6. # because it contains a nested function with free variables  
註釋掉 t2 就沒問題,t2 裡 print 233 也沒錯,t2 裡面為什麼不能引用變量啊! 就算是語法這麼規定的,為什麼我只是定義了一個函數就要報錯? 

How-To 
問題的解決方案參見:In Python, why doesn't exec work in a function with a subfunction?用戶 Lennart Regebro 的回答)exec 的完整語法參見:6.13 The exec statement. 問題的詳細原因參見:PEP 227 -- Statically Nested Scopes,主要是在 Backwards compatibility 小節的第二個例子。 

說一下我對 PEP 227 的理解。python是一門靜態作用域的語言,PEP 227 在 python 2.1 開始引入了一項語言新特性——靜態嵌套作用域。當然也伴隨而來了一些問題。在函數作用域內執行 bare exec 或 import * 的時候,可能會在 local namespace 中創建新的名稱,但由於是動態語句,python 在編譯的時候無法確定這一點。導致的問題就是,python無法在編譯時確定嵌套函數中的名稱綁定。比如下面 inner 函數中的名稱 x,編譯時無法確定是否應該綁定到 global namespace 的名稱 x,因為 exec str 可能會在 local namespace 中也創建名稱 x: 
  1. x = 1  
  2. def outer(str):  
  3.     exec str          # str = 'x=2'  
  4.     def inner():  
  5.         print x  
這種情況下有兩種策略: 
編譯時放任不管,運行時再根據實際情況綁定: 
* 這會導致人在看代碼的時候,也無法確定inner函數內的名稱x到底如何綁定
* 違背了 python 的靜態作用域設計目標

編譯時直接強行靜態綁定,忽略動態語句可能的影響 
* 如果動態語句真的在當前作用域中創建了新的名稱綁定,那這些名稱就對當前作用域中的嵌套作用域不可見了,這違背了PEP 227本身的設計目標

兩種策略各有弊病,所以PEP 227最終決定: 
SyntaxError: unqualified exec is not allowed in function 'xxxx' because it contains a nested function with free variables

根據這個異常提示信息,也可以反過來總結出拋異常的兩個條件: 
* bare exec(unqualified exec),或者 import *
* 嵌套函數內部引用了自由變量,也就是外部名稱

要解決這個問題,只需使用 exec ... in ... 語句就可以了。這樣,exec 語句就能保證不會對 local namespace 產生任何影響(修改locals()不會真的影響局部變量),編譯器就能放心的忽略 exec 語句的影響而直接靜態綁定了,這也是為什麼 exec 需要是個關鍵字而不是庫函數。其實這種方法跟上面說的第二種策略是一個意思,只不過把控制權從編譯器交給了開發者。python 3 中對 exec 語義做了一些修改,就不存在這個問題了,所以它不需要再是關鍵字,變成了一個函數。具體還沒了解過。 

Supplement 
Python 的 exec、eval 詳解

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...