Python 中 eval 與 exec 的相同點和不同點

Python探索牛發表於2024-07-24

相同點

在 Python 中,eval 和 exec 都可以用來執行動態生成(dynamically generated)的程式碼。

兩者在Python 3中的函式宣告基本相同,如下所示:

eval(expression[, globals[, locals]])
exec(object[, globals[, locals]])

其中,輸入引數中,globals 必須是字典(dict)型別,表示全域性空間的變數,若未提供,則透過 globals() 方法獲取全域性變數,若提供的字典型別物件不包含名為__builtins__的鍵,則會在表示式解析前,插入這個鍵,其值設為內建模組 builtins 的引用;而 locals 引數可以是任何可對映型別的物件,表示區域性空間的變數,若未提供,則透過 locals() 方法獲取區域性變數。

不同點

下面從關鍵字型別、第一個輸入引數、內調 compile 函式 這 3 個方面,討論 eval 和 exec 的不同之處。

1. 型別不同

eval 在 Python 2 和 Python 3 中都是函式(function);
而 exec 在 Python 2 中是語句(statement),在 Python 3 中是函式。

2. 第一個輸入引數不同

eval 是 evaluate 的英文簡寫,只能用來計算單獨一個 Python 表示式(expression)的值,返回值是這個表示式的執行結果;在 Python 中,表示式(expression)定義為可以在變數賦值中,進行賦值的物件:

# An expression in Python is whatever you can have as the value in a variable assignment:
a_variable = (anything you can put within these parentheses is an expression)

而 exec 是 execute 的英文簡寫,用來執行 Python 語句(statement),如迴圈語句、try...except...異常處理語句、class 定義、函式定義等,無返回值,即返回值始終為 None。

基本示例,如下所示:

>>> 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

3. compile 函式的模式不同

eval 和 exec 在輸入字串型別時,內部都會首先呼叫 compile 函式編譯為 bytecode,eval 函式對應的模式是 'eval',而 exec 對應的模式是 'exec'。compile 函式用來將輸入引數 source 編譯為 code 物件,具體宣告如下:

compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)

其中,引數 source 通常為包含 Python 原始碼的字串;引數 filename 應該給出從中讀取程式碼的檔案,或一些可識別的值,常常使用 <string> ;引數 mode 指定讀取的程式碼的編譯型別:如果包含多個語句,採用 'exec' 模式,如果只包含單一表示式,則採用 'eval' 模式;可選引數 flags 和 dont_inherit 用來控制 future 模組語句影響原始碼編譯;引數 optimize 指定編譯器的最佳化級別。更多內容參見 compile 官方文件。

採用 'exec' 模式的 compile 函式可以編譯包含任意數量語句的原始碼為 bytecode,隱含返回值總是 None;而採用 'eval' 模式的 compile 函式只可以編譯單一表示式為 bytecode,並返回這個表示式的值。如果在 'eval' 模式下,compile 函式的輸入原始碼中包含語句或任何超出了單一表示式的要求,則會丟擲 SyntaxError 異常。

一些具體示例,如下:

>>> eval(compile('20200926', '<string>', 'exec'))  # code returns None
>>> eval(compile('20200926', '<string>', 'eval'))  # code returns 20200926
20200926
>>> exec(compile('20200926', '<string>', 'eval'))  # code returns 20200926,
>>>                                          # but ignored by exec

#學習中遇到問題沒人解答?小編建立了一個Python學習交流群:531509025

>>> 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 只接收單一表示式(eval accepts only a single expression),只是在字串直接傳遞給 eval 函式時有效。此時內部會使用 compile(source, <string>, 'eval') 編譯為 bytecode。如果一個包含 bytecode 的 code 物件傳遞給 exec 或 eval ,它們的表現是相同,除了 exec 總是會返回 None。所以採用 eval 函式執行帶有語句的字串也是可以的,但需要首先使用 compile 函式將原始碼轉為 bytecode,再傳給 eval 方法。

具體示例,如下:

>>> eval(compile('if 1: print("Hello")', '<string>', 'exec'))
Hello
>>>

相關文章