我一直在研究Python代碼的動態評估,並遇到eval()
和compile()
函數以及exec
語句。 html
有人能夠解釋一下eval
和exec
之間的區別,以及不一樣的compile()
模式如何適應嗎? python
exec用於語句,不返回任何內容。 eval用於表達式,並返回表達式的值。 linux
表達式表示「某事」,而語句表示「作某事」。 express
exec
不是表達式:Python 2.x中的語句和Python 3.x中的函數。 它編譯並當即評估字符串中包含的一條語句或一組語句。 例: c#
exec('print(5)') # prints 5. # exec 'print 5' if you use Python 2.x, nor the exec neither the print is a function there exec('print(5)\\nprint(6)') # prints 5{newline}6. exec('if True: print(6)') # prints 6. exec('5') # does nothing and returns nothing.
eval
是一個內置函數( 不是語句),該函數對一個表達式求值並返回該表達式產生的值。 例: app
x = eval('5') # x <- 5 x = eval('%d + 6' % x) # x <- 11 x = eval('abs(%d)' % -100) # x <- 100 x = eval('x = 5') # INVALID; assignment is not an expression. x = eval('if 1: x = 4') # INVALID; if is a statement, not an expression.
compile
是exec
和eval
的較低版本。 它不會執行或評估您的語句或表達式,但會返回能夠執行此操做的代碼對象。 模式以下: ide
compile(string, '', 'eval')
返回若是完成eval(string)
將會執行的代碼對象。 請注意,您不能在這種模式下使用語句。 僅(單個)表達式有效。 compile(string, '', 'exec')
返回若是執行完exec(string)
後將要執行的代碼對象。 您能夠在此處使用任意數量的語句。 compile(string, '', 'single')
相似於exec
模式,可是它將忽略除第一條語句之外的全部內容。 請注意,帶有結果的if
/ else
語句被視爲單個語句。 基本上, eval
用於EVAL審視大家單個動態生成的Python表達式,以及exec
用於動態生成的Python代碼只爲它的反作用EXEC尤特。 函數
eval
和exec
有如下兩個區別: 工具
eval
只接受單個表達 , exec
能夠採起具備Python語句代碼塊:循環, try: except:
, class
和函數/方法def
initions等。 ui
Python中的表達式就是變量賦值中的值:
a_variable = (anything you can put within these parentheses is an expression)
eval
返回給定表達式的值 ,而exec
忽略其代碼中的返回值,並始終返回None
(在Python 2中,它是一條語句,不能用做表達式,所以它實際上不返回任何內容)。
在1.0-2.7版本中, exec
是一條語句,由於CPython須要爲使用exec
產生反作用的函數生成不一樣類型的代碼對象。
在Python 3中, exec
是一個函數; 它的使用對使用它的函數的已編譯字節碼沒有影響。
所以基本上:
>>> a = 5 >>> eval('37 + a') # it is an expression 42 >>> exec('37 + a') # it is an expression statement; value is ignored (None is returned) >>> exec('a = 47') # modify a global variable as a side effect >>> a 47 >>> eval('a = 47') # you cannot evaluate a statement Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1 a = 47 ^ SyntaxError: invalid syntax
在compile
在'exec'
模式編譯任何數量的語句編譯成字節碼隱含老是返回None
,而在'eval'
模式它編譯單個表達成字節碼一個返回表達式的值。
>>> eval(compile('42', '<string>', 'exec')) # code returns None >>> eval(compile('42', '<string>', 'eval')) # code returns 42 42 >>> exec(compile('42', '<string>', 'eval')) # code returns 42, >>> # but ignored by exec
在'eval'
模式下(若是傳入了字符串,則使用eval
函數),若是源代碼包含語句或除單個表達式以外的其餘任何內容,則compile
會引起異常:
>>> compile('for i in range(3): print(i)', '<string>', 'eval') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1 for i in range(3): print(i) ^ SyntaxError: invalid syntax
實際上,只有將字符串(包含Python 源代碼 )傳遞給eval
時,語句「 eval只接受一個表達式」才適用。 而後使用compile(source, '<string>', 'eval')
在內部將其編譯爲字節碼。這纔是真正的區別。
若是將code
對象(包含Python bytecode )傳遞給exec
或eval
,則它們的行爲相同 ,除了exec
忽略返回值的事實外,始終始終返回None
。 所以,若是您只是將compile
爲字節碼而不是將其做爲字符串傳遞,則可使用eval
執行具備語句的內容:
>>> eval(compile('if 1: print("Hello")', '<string>', 'exec')) Hello >>>
即便已編譯的代碼包含語句,也能夠正常工做。 它仍然返回None
,由於那是從compile
返回的代碼對象的返回值。
在'eval'
模式下(若是傳入了字符串,則使用eval
函數),若是源代碼包含語句或除單個表達式以外的其餘任何內容,則compile
會引起異常:
>>> compile('for i in range(3): print(i)', '<string>'. 'eval') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1 for i in range(3): print(i) ^ SyntaxError: invalid syntax
exec
和eval
exec
函數( 在Python 2中爲語句 )用於執行動態建立的語句或程序:
>>> program = ''' for i in range(3): print("Python is cool") ''' >>> exec(program) Python is cool Python is cool Python is cool >>>
eval
函數對單個表達式執行相同的操做, 並返回表達式的值:
>>> a = 2 >>> my_calculation = '42 * a' >>> result = eval(my_calculation) >>> result 84
exec
和eval
都接受程序/表達式以包含源代碼的str
, unicode
或bytes
對象或包含Python字節碼的code
對象運行 。
若是將包含源代碼的str
/ unicode
/ bytes
傳遞給exec
,則其行爲等效於:
exec(compile(source, '<string>', 'exec'))
和eval
相似地等效於:
eval(compile(source, '<string>', 'eval'))
因爲全部表達式均可以用做Python中的語句(在Python 抽象語法中 ,這些表達式稱爲Expr
節點;反之則不成立),若是不須要返回值,則始終可使用exec
。 也就是說,您可使用eval('my_func(42)')
或exec('my_func(42)')
,不一樣之處在於eval
返回my_func
返回的值,而exec
則將其丟棄:
>>> def my_func(arg): ... print("Called with %d" % arg) ... return arg * 2 ... >>> exec('my_func(42)') Called with 42 >>> eval('my_func(42)') Called with 42 84 >>>
在2個程序中,只有exec
接受包含語句的源代碼,例如def
, for
, while
, import
或class
,賦值語句(aka a = 42
)或整個程序:
>>> exec('for i in range(3): print(i)') 0 1 2 >>> eval('for i in range(3): print(i)') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1 for i in range(3): print(i) ^ SyntaxError: invalid syntax
雙方exec
和eval
接受2個額外的位置參數- globals
和locals
-這是全局和局部變量的做用域,該代碼看到。 它們默認爲exec
或eval
範圍內的globals()
和locals()
,可是任何字典均可以用於globals
以及任何locals
mapping
(固然包括dict
)。 這些不只能夠用於限制/修改代碼中看到的變量,並且還常常用於捕獲exec
代碼建立的變量:
>>> g = dict() >>> l = dict() >>> exec('global a; a, b = 123, 42', g, l) >>> g['a'] 123 >>> l {'b': 42}
(若是顯示整個g
的值,則將花費更長的時間,由於exec
和eval
會將內置模塊做爲__builtins__
到全局變量中(若是缺乏)。
在Python 2中, exec
語句的正式語法實際上exec code in globals, locals
,如
>>> exec 'global a; a, b = 123, 42' in g, l
可是,替代語法exec(code, globals, locals)
也一直被接受(見下文)。
compile
內置的compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
能夠經過預先將源code
編譯爲code
對象來使用exec
或eval
來加快相同代碼的重複調用。 mode
參數控制compile
功能接受的代碼片斷的類型及其生成的字節碼的類型。 選擇是'eval'
, 'exec'
和'single'
:
'eval'
模式須要一個表達式,並會生成字節碼,運行時將返回該表達式的值:
>>> dis.dis(compile('a + b', '<string>', 'eval')) 1 0 LOAD_NAME 0 (a) 3 LOAD_NAME 1 (b) 6 BINARY_ADD 7 RETURN_VALUE
'exec'
接受從單一表達式到整個代碼模塊的各類python構造,並像將其做爲模塊頂級語句同樣執行它們。 代碼對象返回None
:
>>> dis.dis(compile('a + b', '<string>', 'exec')) 1 0 LOAD_NAME 0 (a) 3 LOAD_NAME 1 (b) 6 BINARY_ADD 7 POP_TOP <- discard result 8 LOAD_CONST 0 (None) <- load None on stack 11 RETURN_VALUE <- return top of stack
'single'
是'exec'
的有限形式,它接受包含單個語句(或多個用;
分隔的語句)的源代碼,若是最後一條語句是一個表達式語句,則生成的字節碼也將打印該表達式值的repr
到標準輸出(!) 。
一個if
- elif
- else
鏈,一個else
循環,並try
使用其except
, else
和finally
塊被視爲單個語句。
包含2個頂級語句的源代碼片斷是'single'
的錯誤,除了在Python 2中存在一個錯誤 , 該錯誤有時會在代碼中容許多個頂級語句。 只有第一個被編譯; 其他的將被忽略:
在Python 2.7.8中:
>>> exec(compile('a = 5\\na = 6', '<string>', 'single')) >>> a 5
在Python 3.4.2中:
>>> exec(compile('a = 5\\na = 6', '<string>', 'single')) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1 a = 5 ^ SyntaxError: multiple statements found while compiling a single statement
這對於製做交互式Python Shell很是有用。 可是,即便eval
結果代碼,也不會返回表達式的值。
所以, exec
和eval
最大區別實際上來自於compile
功能及其模式。
除了將源代碼編譯爲字節碼外, compile
支持將抽象語法樹 (Python代碼的解析樹)編譯爲code
對象。 並將源代碼轉換成抽象語法樹( ast.parse
用Python編寫,僅調用compile(source, filename, mode, PyCF_ONLY_AST)
); 它們被用於例如動態修改源代碼,以及用於動態代碼建立,由於在複雜狀況下,將代碼做爲節點樹而不是文本行來處理一般會更容易。
雖然eval
僅容許您評估包含單個表達式的字符串,可是您能夠eval
整個語句,甚至eval
已經compile
爲字節碼的整個模塊; 也就是說,在Python 2中, print
是一條語句,不能直接eval
:
>>> eval('for i in range(3): print("Python is cool")') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1 for i in range(3): print("Python is cool") ^ SyntaxError: invalid syntax
用'exec'
模式將其compile
爲code
對象,而後能夠進行eval
; eval
函數將返回None
。
>>> code = compile('for i in range(3): print("Python is cool")', 'foo.py', 'exec') >>> eval(code) Python is cool Python is cool Python is cool
若是在CPython 3中研究eval
和exec
源代碼,這是很是明顯的; 它們都使用相同的參數調用PyEval_EvalCode
,惟一的區別是exec
顯式返回None
。
exec
語法差別 Python 2的主要區別之一是exec
是一個語句, eval
是一個內置函數(這兩個都是Python 3中的內置函數)。 這是一個衆所周知的事實,正式語法exec
在Python 2 exec code [in globals[, locals]]
。
與大多數Python 2到3 移植 指南 彷佛 建議的不一樣 ,CPython 2中的exec
語句也能夠與看起來 徹底像Python 3中的exec
函數調用的語法一塊兒使用。緣由是Python 0.9.9具備exec(code, globals, locals)
內置函數! 而且該內置函數在python 1.0發佈以前的某個地方被exec
語句替換了。
因爲但願不破壞與Python 0.9.9的向後兼容性,所以Guido van Rossum在1993年添加了一個兼容性黑客 :若是code
是長度爲2或3的元組,不然不會將globals
和locals
傳遞給exec
語句,該code
將被解釋爲好像元組的第2個元素和第3個元素分別是globals
和locals
。 即便在Python 1.4文檔(在線最先可用的版本)中也沒有提到兼容性hack; 所以對於移植指南和工具的許多做者並不瞭解,直到2012年11月再次對其進行了記錄 :
第一個表達式也能夠是長度爲2或3的元組。在這種狀況下,必須省略可選部分。 形式
exec(expr, globals)
等同於exec expr in globals
,而形式exec(expr, globals, locals)
等同於exec expr in globals, locals
。exec
的元組形式提供了與Python 3的兼容性,其中exec
是函數而不是語句。
是的,在CPython 2.7中它被方便地稱爲前向兼容選項(爲何令人們感到困惑,由於根本就沒有向後兼容選項),而實際上它已經存在了二十年 。
所以,雖然exec
是Python 1和Python 2中的語句,而Python 3和Python 0.9.9中是內置函數,
>>> exec("print(a)", globals(), {'a': 42}) 42
在可能的每一個普遍發行的Python版本中都具備相同的行爲; 而且也能夠在Jython 2.5.2,PyPy 2.3.1(Python 2.7.6)和IronPython 2.6.1中使用(對它們的嚴格遵循CPython的未記錄的行爲表示敬意)。
在具備兼容性問題的Python 1.0-2.7中,您沒法執行的操做是將exec
的返回值存儲到變量中:
Python 2.7.11+ (default, Apr 17 2016, 14:00:29) [GCC 5.3.1 20160413] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> a = exec('print(42)') File "<stdin>", line 1 a = exec('print(42)') ^ SyntaxError: invalid syntax
(這在Python 3中也沒有用,由於exec
老是返回None
),或者將引用傳遞給exec
:
>>> call_later(exec, 'print(42)', delay=1000) File "<stdin>", line 1 call_later(exec, 'print(42)', delay=1000) ^ SyntaxError: invalid syntax
某人可能實際使用過的一種模式,儘管可能性不大;
或在列表理解中使用它:
>>> [exec(i) for i in ['print(42)', 'print(foo)'] File "<stdin>", line 1 [exec(i) for i in ['print(42)', 'print(foo)'] ^ SyntaxError: invalid syntax
這是對列表理解的濫用(請改用for
循環!)。