基本上 eval 上用來評估一個動態生成的 Python 表達式;exec 額外的用於執行動態生成的 python 代碼。
eval 和 exec 有以下兩個差異
eval 只接受一個表達式,exec 可以接受一個包含了 python 語句的代碼塊: loops, try: except:, class 以及定義的函數和方法
Python 中的表達式是任何可以作為變量賦值中的值的表達式:
a_variable = (任何你可以放在這個括號內的都是一個表達式)
evale 返回表達式的值,而 exec 忽略返回值的代碼,並且使用返回 None(在 Python 2 中這是一個聲明,而不是一個表達式,所以沒有任何返回)
在 1.0 - 2.7 版本中,exec 是一個聲明,它總是為函數產生相同類型的代碼對象,因為需要使用 exec 在函數中產生一個不同的編碼對象。
在 python3 中,exec 上一個函數,它總是產生相同類型的代碼對象,不管是在函數內部還是模塊范圍內調用的。
因此,基本上是這樣的
>>> a = 5 >>> eval('37 + a') # 這是一個表達式 42 >>> exec('37 + a') # 這時一個表達式聲明,返回值被忽略了(因為返回的是 None ) >>> exec('a = 47') # 修改一個全局變量 >>> a 47 >>> eval('a = 47') # 你無法評估一個表達式 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1 a = 47 ^ SyntaxError: invalid syntax
在'exec'模式下編譯,將任意數量的語句編譯成 總是返回None的字節碼,而在'eval'模式下,它將單個表達式編譯成返回該表達式值的字節碼。
>>> eval(compile('42', '<string>', 'exec')) # 代碼返回 None >>> eval(compile('42', '<string>', 'eval')) # 代碼返回42 42 >>> exec(compile('42', '<string>', 'eval')) # 代碼返回 42,但是被 exec 忽略了
在“eval”模式下(如果傳入字符串,則使用eval函數),如果源代碼包含語句或其他超出單個表達式的語句,則編譯會引發異常:
>>> 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
實際上, 這個聲明 “eval 只接受一個表達式” 只有當 一個包含python代碼的字符串傳入進來是,他才會內部進行編譯,生成字節碼,compile(source, '<string>', 'eval'),這是差異真正來自的地方。
如果一個包含了 python 字節碼的編碼對象被傳遞給了 exec 和 eval ,他們的行為是一樣的,區別僅僅在於 exce 忽略返回值,仍然返回 None。
所以如果你只想編譯城字節碼而不是作為字符串傳遞的化,可以使用 eval 來執行包含語句的東西。
>>> eval(compile('if 1: print("Hello")', '<string>', 'exec'))
Hello
即使編譯的代碼包含語句,也可以毫無問題地工作。 它仍然返回None,因為這是從編譯返回的代碼對象的返回值。
在 eval 模式下(如果傳入字符串,則使用eval函數),如果源代碼包含語句或其他超出單個表達式的語句,則編譯會引發異常。
>>> 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 and 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字節碼的代碼對象運行。
如果將包含源代碼的str / unicode / bytes傳遞給exec,則它的行為等同於:
exec(compile(source, '<string>', 'exec'))
和eval類似的表現等同於:
eval(compile(source, '<string>', 'eval'))
由於所有表達式都可以在Python中用作語句(這些語句在Python抽象語法中被稱為Expr節點;反之亦然),如果不需要返回值,則可以始終使用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
>>>
在 Python 2 中,只有exec接受包含語句(如def,for,while,import或class),賦值語句(a.k.a 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個額外的位置參數 - 全局變量和局部變量 - 這是代碼所看到的全局和局部變量范圍。 這些默認為調用exec或eval的范圍內的globals()
和locals()
,但任何字典都可以用於全局變量和任何本地映射(當然包括字典)。 這些不僅可以用來限制/修改代碼所看到的變量,還可以用於捕獲執行代碼創建的變量:
>>> 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語句的官方語法實際上是globals,locals中的exec代碼,如
>>> 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)
可以用來預先將源代碼編譯成代碼對象,從而加速exec或eval對同一代碼的重復調用。mode參數控制編譯函數接受的代碼片段的類型以及它產生的字節碼的類型。 選擇是'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 塊被視為是一個單獨的語句。
在 “single”中包含2個頂級語句的源代碼片段是錯誤的,除了在Python 2中有一個錯誤,有時候允許代碼中有多個頂層語句; 只有第一個是編譯的; 其余的被忽略:
In Python 2.7.8:
>>> exec(compile('a = 5\na = 6', '<string>', 'single')) >>> a 5
And in 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的最大區別實際上來自編譯函數及其模式。
編譯器除了將源代碼編譯成字節碼之外,還支持將抽象語法樹(將Python代碼解析樹)編譯成代碼對象; 和源代碼到抽象語法樹(ast.parse
是用Python編寫的,只是調用了compile(source, filename, mode, PyCF_ONLY_AST))
這些例如用於動態修改源代碼,也用於動態代碼創建,因為在復雜的情況下將代碼作為節點樹而不是文本行處理通常更容易。
雖然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
成代碼對象,然后你可以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。
Python 2和Python 3之間exec的語法差異
一個在Python 2的主要差異是,exec
一個聲明和eval
是一個內置函數(無論是在Python 3的內置函數)。這是一個眾所周知的事實,在Python 2執行正式的語法是全局執行代碼 exec code [in globals[, locals]]
。
不像大多數Python 2-3移植指南似乎表明,在CPython 2 EXEC語句也可以使用語法,看起來就像在Python 3 exec函數調用。原因是,Python 0.9.9有exec(代碼、全局變量、局部變量)內置函數!在Python 1發布之前,內置的函數被替換了。
因為它是可取的不是Python 0.9.9打破向后兼容性,Guido van Rossum說1993的兼容性的問題:如果代碼是一個元組長度為2或3,與全球和當地人不傳遞給exec語句否則,代碼將被解釋為如果元組的第二元和第三元的全局變量和當地人的分別。即使在Python 1.4文檔(最早可用的在線版本)中也沒有提到兼容性破解程序,因此許多移植指南和工具的作者都不知道,直到2012年11月再次被證明:
第一表達式也可以是長度為2或3的元組。在這種情況下,必須省略可選部件。 exec(expr, globals)
相當於執行於全局,而形式exec(expr,全局變量、當地人)相當於執行於全局,當地人。執行器的元組形式提供了與Python 3的兼容性,在這里Python是一個函數而不是一個語句。
是的,在當前的2.7,是輕易被提到的向前兼容性選項(為什么迷惑人,都是一個向后兼容性選項),當它實際上已經有二十多年的向后兼容性。
因此,執行是Python 1和Python 2的聲明,並在Python 3和Python 0.9.9內置函數,
>>> exec("print(a)", globals(), {'a': 42})
42
是的,在當前的2.7,是輕易被提到的向前兼容性選項(為什么迷惑人,都是一個向后兼容性選項),當它實際上已經有二十多年的向后兼容性。
因此,執行是Python 1和Python 2的聲明,並在Python 3和Python 0.9.9內置函數,
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:
哪一種模式可能是某人實際使用過的,雖然不太可能;
或者在列表理解中使用它:
>>> [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
這是列表解析濫用(使用循環取反!)。