前陣子一直在思考一個問題,就是如何讓用戶在圖形界面上輸入的代碼(輸入的代碼為字符串),成為代碼的一部分而運行起來,恰逢看python爬蟲的視頻教程的時候,看到了使用eval函數,之后查找到了該文章,解決了我思考的問題。
@文章來源:https://my.oschina.net/duhaizhang/blog/66048
Python有時需要動態的創造Python代碼,然后將其作為語句執行 或 作為表達式計算。
exec用於執行存儲在字符串中的Python代碼。
1、 語句與表達式的區別:表達式是 某事,語句是 做某事(即告訴計算機做什么)。
比如2*2是4,而print 2*2是打印4。上述兩句代碼在交互式解釋器中執行的結果是一樣的,是因為解釋器總是把所有表達式的值打印出來而已。而在程序中編寫類似2*2這樣的表達式並不會打印顯示什么,編寫print 2*2則會打印4。
語句與表達式的區別在賦值時更明顯,因為語句不是表達式,所以沒有值。如在交互式解釋器中輸入 x=2則不會打印任何東西,立刻出現新的提示符。雖然什么也沒顯現,但是有些東西已經發生變化如x的值現在變為3.這也是語句特性的一般定義:它們改變了事物。比如,賦值語句改變了變量,print語句改變了屏幕顯示的內容。
2、 命名空間(作用域) 全局變量和局部變量
除了全局作用域外,每個函數會都會創建一個新的作用域。變量分為全局變量和局部變量,函數內的變量稱為局部變量只在局部命名空間中起作用。
在函數內部讀取全局變量一般來說不是問題,直接訪問即可。但是,如果局部變量名或者參數的名字與全局變量名相同的話,就不能直接訪問了,因為全局變量被局部變量給屏蔽了。如果確實需要的話,可以使用globals函數獲取被屏蔽的全局變量值。(globals返回全局變量的字典,locals返回局部變量的值)。例如:有一個名為parameter的全局變量,那么在combine(parameter)函數內部訪問全局變量時,因為與參數重名,必須使用globals()['parameter']獲取。代碼如下:
def combine(parameter): print parameter+globals()['parameter'] #函數調用 parameter="hello" combine("berry")
上面講的是再函數內部讀取全局變量的方法,不包括修改。如果要在函數內部修改全局變量,需要告知修改的值是全局變量,因為在函數內部將值賦予一個變量那么變量自動成為局部變量。通過global關鍵字來告訴Python函數內一個需要修改的變量是一個全局變量。代碼如下:
x=1 def change_global(n): global x x=x+1
3、執行字符串的語句 exec
如輸入exec "print 'hello'"會打印出hello。(注意:Python 3.0中,exec是一個函數不是一個語句了,因此使用exec('字符串語句')的方式來調用)。exec執行字符串語句存在安全風險,因為exec可能會干擾命名空間,即改變不應該變的變量。例如:
從上面的例子可以看出,exec干擾了命名空間,改變了sqrt的值,使其不是一個函數而變成1了。由此可見,如果對exec不加限制就會存在安全風險。下面是改進措施。
措施:通過增加 in <scope>來實現,其中的<scope>是一個字典,該字典起到放置代碼字符串命名空間的作用。這樣exec執行的代碼只會在<scope>代表的命名空間中起作用。如:
從上面代碼中可以看到,exec語句在scope命名空間中執行,不會影響到現在命名空間的sqrt。scope雖然充當命名空間的作用,但實質仍是一個字典,所以如果想知道scope命名空間中有多少變量時,可通過len(scope)獲得,可通過scope.key()獲得scope命名空間的所有變量。
4、eval 會計算字符串形式的Python表達式,並返回結果的值。
exec語句不會返回任何對象。而eval會返回表達式的值。下面的代碼可以創建一個Python計算器:
#Python計算器 print eval(raw_input("Please input an arithmetic expression:"))
上面代碼解釋,上面代碼中eval內部現在還不是字符串,首先執行raw_input()函數,raw_input()返回你輸入的求值字符串,現在eval函數內部就是求值字符串了,就可以用eval進行字符串的求值了。如輸入:4*5+6,那么raw_input就會返回“4*5+6”,eval求值后為26.
要注意上面代碼與下面代碼的區別:
print eval('raw_input("Please input an arithmetic expression:")')
在這個代碼中,與Python計算器代碼不同的是,eval函數內直接就是字符串,那么直接對字符串求值,但是字符串中是raw_input表達式,raw_input表達式將用戶的輸入轉換為字符串,所以如果輸入4+5的話會返回"4+5"。注意:raw_input('xxxxx')是一個表達式,表達式的值就是用戶輸入。 可能疑惑的是代碼:exec('raw_input("Please input an arithmetic expression:")')不會報錯,因為ecec也可以用於表達式,只是什么效果也沒達到而已(既不返回值,也沒干事情)。
跟exec一樣,eval也可以使用命名空間。因為盡管表達式一般不會給變量重新賦值,但是表達式可以通過調用函數來達到給全局變量賦值的目的。例如執行下面代碼后,全局變量x的值會被重新賦值為2:
x=1 def inc_x(): global x x=x+1 eval("inc_x()") print x
從上面的代碼可以看出eval函數也是不安全的,必須使用命名空間。事實上,可以為eval提供兩個命名空間,一個是全局的,另一個是局部的。全局的必須是字典,局部的可以是任何形式的映射。
exec和eval的命名空間使用代碼(命名空間可以不是空的字典,可以提前為命名空間提供一些值):
scope={} scope['x']=1 scope['y']=2 print eval('x+y',scope) scope={} exec "x=2" in scope eval("x*x",scope)