首頁  >  文章  >  後端開發  >  怎麼使用Python eval函數

怎麼使用Python eval函數

WBOY
WBOY轉載
2023-06-04 09:19:554227瀏覽

Python 的 eval()

我們可以使用內建的 Python eval()[1] 從基於字串或基於編譯程式碼的輸入中動態地計算表達式。如果我們向 eval() 傳遞一個字串,那麼該函數會解析它,將其編譯為字節碼[2],並將其作為一個 Python 表達式進行計算。但如果我們用一個編譯過的程式碼物件呼叫 eval(),那麼函數只執行計算步驟,如果我們用相同的輸入多次呼叫 eval(),這就非常方便了。

Python的 eval() 的定義如下。

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

此函數需要一個第一個參數,稱為expression,它包含了需要計算的表達式。 eval()還需要兩個可選參數。

  1. globals

  2. locals

在接下來的內容中,我們將學習這些參數是什麼,以及eval() 如何使用它們來即時計算Python 表達式。

請注意:Python程式碼可以透過exec()[3]來實現動態執行。 eval() 和 exec() 的主要差異是,eval() 只能執行或計算表達式,而 exec() 可以執行任何一段 Python程式碼。

第一個參數:expression

eval() 的第一個參數稱為expression,它是一個必需的參數,用於保存函數的基於字串或基於編譯碼的輸入。使用 eval() 函數時,Python 將計算 expression 作為一條表達式。下面是使用基於字串的輸入的例子。

>>> eval("2 ** 8")
256
>>> eval("1024 + 1024")
2048
>>> eval("sum([8, 16, 32])")
56
>>> x = 100
>>> eval("x * 2")
200

當用一個字串作為參數呼叫 eval() 時,函數會傳回輸入字串進行計算的結果。預設情況下,eval()可以存取全域變數名,如上例中的x。

為了計算一個基於字串的表達式,Python 的 eval() 執行下列步驟。

  1. 解析表達式

  2. 將其編譯為字節碼

  3. 將其作為一個Python表達式進行計算

  4. 傳回計算的結果

函數eval()的第一個參數expression強調了它只用於表達式,而不是複合語句[4]。 Python 文檔對 expression 的定義如下。

expression

一段可以被計算為某種值的語法。表達式是由多個元素構成的,這些元素包括字面意義、名稱、屬性存取、運算子或函數呼叫等,它們的累積結果是一個值。與許多其他語言相比,並非所有的語言結構都是表達式。也有一些語句不能當作表達式使用,如 while。另外賦值也是語句,不是表達式。

另一方面,Python statement 有下列定義。

statement

Statement is a part of a suite (a block of code).。 statement要么是一個表達式,要么是帶有關鍵字的幾個結構體之一,如 if、while或for。

如果向eval()傳遞一個複合語句,那麼會得到一個 SyntaxError。下面的例子是用eval()來執行一個if語句。

>>> x = 100
>>> eval("if x: print(x)")
File "", line 1
if x: print(x)
^
SyntaxError: invalid syntax

上面報錯是因為 eval() 只接受表達式。如果使用任何其他語句,例如if、for、while、import、def或class,就會產生錯誤。

注意: for 迴圈是一個複合語句,但是 for 關鍵字也可以用在推導式中,此時它被認為是表達式。 For loop keywords can be used in comprehension expressions and evaluated using eval().。

eval()也不允許進行賦值運算。

>>> eval("pi = 3.1416")
File "", line 1
pi = 3.1416
 ^
SyntaxError: invalid syntax

當我們將賦值運算當作 eval() 的參數時,會發生語法錯誤(SyntaxError)。賦值運算是語句,而不是表達式,語句不允許與 eval() 一起使用。

如果輸入的表達式無法被解析器理解,那麼可能會觸發 SyntaxError。在下面的範例中計算一個違反 Python 語法的表達式。

>>> # Incomplete expression
>>> eval("5 + 7 *")
File "", line 1
5 + 7 *
^
SyntaxError: unexpected EOF while parsing

所以,不能把一個違反 Python 語法的表達式傳給 eval() 。在上面的範例中,我們嘗試計算一個不完整的表達式 ("5 7 *") 時拋出一個 SyntaxError,因為分析器不理解表達式的語法。

我們也可以把已編譯的程式碼物件傳遞給 eval() 。因此可以使用函數 compile()[7] ,一個內建函數,可以將輸入的字串編譯成程式碼物件[8] 或 AST 物件[9],這樣就可以用 eval() 來計算它。

如何使用compile()的細節超出了本文的範圍,但這裡可以快速了解它的前三個必要參數。

source保存我們要編譯的原始碼。這個參數可以接受普通字串、位元組字串[10]和AST物件。

filename給出讀取程式碼的檔案。如果我們要使用一個基於字串的輸入,那麼這個參數的值應該是""。

mode指定了我們想要得到哪一種編譯後的程式碼。如果要使用eval()來處理編譯後的程式碼,則需要將參數設為"eval"。

我們可以使用 compile() 向eval()提供程式碼對象,而不是普通的字串。

>>> # 算术运算
>>> code = compile("5 + 4", "", "eval")
>>> eval(code)
9
>>> code = compile("(5 + 7) * 2", "", "eval")
>>> eval(code)
24
>>> import math
>>> # 一个球体的体积
>>> code = compile("4 / 3 * math.pi * math.pow(25, 3)", "", "eval")
>>> eval(code)
65449.84694978735

当我们通过 compile() 进行表达式编译后,eval() 将按照以下顺序执行。

  1. 计算编译后的代码

  2. 返回计算的结果

如果使用编译码为输入并调用 eval(),那么该函数将执行操作并立即返回结果。当需要多次计算同一个表达式时,这可能很方便。为了最佳效果,在接下来的 eval() 调用中,最好预先编译表达式并重用所生成的字节码。

预编译输入表达式后连续多次调用eval()的执行速度更快,因为省略了重复的解析和编译步骤。当计算复杂的表达式时,不必要的重复计算会大大增加CPU时间和内存消耗。

第二个参数:globals

eval() 的第二个参数 globals,可选的,字典类型,为 eval() 提供一个全局命名空间。使用 globals 可以指定在 eval() 计算表达式时使用哪些全局变量名。

全局变量名是所有那些在当前全局范围或命名空间中可用的变量名。可以从代码的任何地方访问它们。

所有在 globals 中传递给字典的名称都可以在 eval() 执行时提供。下面是一个例子,展示如何利用自定义字典为 eval() 提供全局命名空间。

>>> x = 100# 一个全局变量
>>> eval("x + 100", {"x": x})
200
>>> y = 200# 另一个全局变量
>>> eval("x + y", {"x": x})
Traceback (most recent call last):
File "", line 1, inFile "", line 1, inNameError: name 'y' is not defined

如果为 eval() 的 globals 参数提供一个自定义字典,那么 eval() 将只接受这些名字作为 globals。在这个自定义字典之外定义的任何全局变量名都不能从 eval() 内部访问。这就是为什么当你试图在上述代码中访问 y 时,Python 会引发一个 NameError。传递给 globals 的字典不包括 y。

可以通过在字典中列出名字来插入 globals,然后这些名字在求值过程中就会出现。例如,如果在 globals 中插入了 y,那么在上面的例子中对 "x + y" 的求值将如期进行。

>>> eval("x + y", {"x": x, "y": y})
300

因为把 y 添加到了自定义 globals 字典中,所以成功计算 "x + y" 的值,得到的预期返回值 300。

我们也可以提供不存在于当前全局范围的变量名。此时需要为每个名字提供一个具体的值。eval()在运行时将把这些变量名解释为全局变量名。

>>> eval("x + y + z", {"x": x, "y": y, "z": 300})
600
>>> z
Traceback (most recent call last):
File "", line 1, inNameError: name 'z' is not defined

尽管z没有在当前的全局范围内定义,但是这个变量在全局中的值是300,此时eval()可以访问z,就像它是一个全局变量一样。

globals 背后的机制是相当灵活的,可以向 globals 传递任何可见的变量(全局、局部、或者非局部)。还可以传递自定义的键值对,比如上面例子中的 "z": 300,那么eval() 将把它们全部作为全局变量处理。

关于 globals 中的注意事项,如果我们提供给它的自定义字典不包含键值 "__builtins__",那么在表达式被解析之前,对内置字典的引用将自动插入 "__builtins__" 下面。这可以确保 eval() 在计算表达式时可以完全访问所有的 Python 内置变量名。

下面的例子表明,即使给 globals 提供了一个空的字典,对 eval() 的调用仍然可以访问 Python 的内置变量名。

>>> eval("sum([2, 2, 2])", {})
6
>>> eval("min([1, 2, 3])", {})
1
>>> eval("pow(10, 2)", {})
100

我们已经提供了一个空字典 ({}) 给 globals 在上述代码中。由于这个字典不包含一个叫做 "__builtins__" 的键,Python 会自动插入一个指向 builtins 中名字的引用。通过这种方式,eval() 能够完全获取到所有 Python 内置名称,以便解析表达式。

如果调用 eval() 而没有将自定义字典传递给 globals ,那么参数将默认为在调用 eval()的环境中 globals() 返回的字典:

>>> x = 100#一个全局变量
>>> y = 200# 另一个全局变量
>>> eval("x + y")# 访问两个全局变量
300

当调用 eval() 而不提供 globals 参数时,该函数使用 globals() 返回的字典作为其全局命名空间来计算表达式。在上述示例中,x和y是全局变量,在当前的全局范围内可以随意访问。

第三个参数:locals

Python 的函数 eval() 可以通过第三个可选参数 locals 传入一个字典类型的参数。此时这个字典包含了 eval() 在计算表达式时作为局部变量名使用的变量。

我们在一个函数内定义的名称(变量、函数、类等等)就是局部变量名。局部名称只在封闭的函数内可见。我们在编写函数时定义这些变量名。

在 eval() 的代码或局部范围内添加局部变量名是不允许的,因为 eval() 已经被编写完成。可以通过将字典传递给locals,让eval()将这些名称视为本地名称。

>>> eval("x + 100", {}, {"x": 100})
200
>>> eval("x + y", {}, {"x": 100})
Traceback (most recent call last):
File "", line 1, inFile "", line 1, inNameError: name 'y' is not defined

第一个调用 eval() 的第二个字典保存了变量 x。这个变量被 eval() 解释为一个局部变量。换句话说,它被看作是在 eval() 中定义的一个变量。

我们可以在表达式中使用 x,并且 eval() 可以访问它。相反,如果使用y,那么会得到一个 NameError,因为y没有定义在 globals 命名空间或 locals 命名空间。

和 globals 一样,可以向 locals 传递任何可见的变量(全局、局部或非局部)。也可以传递自定义的键值对,比如 "x"。eval()将把它们全部作为局部变量处理。

注意,要给 locals 提供一个字典,首先需要给 globals 提供一个字典。不能在 eval() 中使用关键字参数。

>>> eval("x + 100", locals={"x": 100})
Traceback (most recent call last):
File "", line 1, inTypeError: eval() takes no keyword arguments

若在使用关键字参数来调用 eval(),则会引发 TypeError 异常。这是因为 eval() 不接受关键字参数,所以在提供 locals 字典之前,需要先提供一个 globals 字典。

如果没有传递字典给 locals 参数,它将默认使用传递给 globals 参数的字典。这里有一个例子,给 globals 传递了一个空的字典,而 locals 没有传递任何值。

>>> x = 100
>>> eval("x + 100", {})
Traceback (most recent call last):
File "", line 1, inFile "", line 1, inNameError: name 'x' is not defined

如果没有为locals参数提供自定义字典,则它将默认传递给globals参数的字典。因为 globals 持有空字典,所以现在无法通过 eval() 访问 x。

globals 和 locals 之间的主要实际区别是,如果"__builtins__"键不存在,Python 会自动插入 globals 中。这个事件将会发生,不论我们是否提供一个定制的字典给 globals。此外,如果我们给 locals 提供了一个自定义的字典,那么在执行 eval() 的过程中,这个字典将保持不变。

用 eval() 计算表达式

我们可以使用Python的eval()来计算任何一种Python表达式,但不包括Python语句,如基于关键字的复合语句或赋值语句。

当我们需要动态地计算表达式,而使用其它 Python 技术或工具会大大增加我们的开发时间和精力时,eval() 可以很方便。

在这一节中,我们将学习如何使用 Python 的 eval() 来计算布尔、数学和通用的 Python 表达式。

布尔表达式

布尔表达式 是Python表达式,当解释器对其进行计算时返回一个真值(True 或者 False)。它们通常用在if语句中,以检查某些条件是否为真或假。由于布尔表达式不是复合语句,我们可以使用eval()来计算它们。

>>> x = 100
>>> y = 100
>>> eval("x != y")
False
>>> eval("x < 200 and y > 100")
False
>>> eval("x is y")
True
>>> eval("x in {50, 100, 150, 200}")
True

我们可以用 eval() 来处理使用以下任何Python运算符的布尔表达式。

  • 值比较运算符:28675c3d02b76e47536b84c673e04c48 ,

    ea2e6b7668fd5e472327f7c8970e7490=, ==, !=

  • 逻辑(布尔)运算符:and,or,not

  • 成员测试运算符:in,not in

  • 身份运算符:is,is not

在所有情况下,该函数都会返回正在计算的表达式的真值。

我们思考,为什么我应该使用eval()而不是直接使用布尔表达式呢?假设需要实现一个条件语句,但我们想临时改变条件。

>>> def func(a, b, condition):
... if eval(condition):
... return a + b
... return a - b
...
>>> func(2, 4, "a > b")
-2
>>> func(2, 4, "a < b")
6
>>> func(2, 2, "a is b")
4

Func() calculates the supplied condition using eval(), and returns a+b or a-b based on the result of the calculation.。在上面的例子中,只使用了几个不同的条件,但还可以使用任何数量的其他条件,只要坚持使用我们在func()中定义的名称a和b。

现在想象一下,如果不使用Python的eval(),我们将如何实现这样的东西。那会花更少的代码和时间吗?不可能!

数学表达式

常见的Python eval()用例之一是用于计算基于字符串的数学表达式。举个例子,我们可以创建一个 Python 计算器,利用 eval() 对用户输入进行计算,并返回结果。

下面的例子演示了如何使用eval()与数学一起进行math运算。

>>> # Arithmetic operations
>>> eval("5 + 7")
12
>>> eval("5 * 7")
35
>>> eval("5 ** 7")
78125
>>> eval("(5 + 7) / 2")
6.0
>>> import math
>>> # 一个圆的面积
>>> eval("math.pi * pow(25, 2)")
1963.4954084936207
>>> # 球体的体积
>>> eval("4 / 3 * math.pi * math.pow(25, 3)")
65449.84694978735
>>> # 直角三角形的斜边
>>> eval("math.sqrt(math.pow(10, 2) + math.pow(15, 2))")
18.027756377319946

当我们使用eval()来计算数学表达式时,我们可以传入任何种类或复杂程度的表达式,eval()会解析它们,计算它们,如果一切正常,就会给我们预期结果。

通用表达式

前面我们已经学会了如何在布尔和 math 表达式中使用 eval() 。然而,我们可以在更复杂的 Python 表达式中使用 eval() ,这些表达式包括函数调用、对象创建、属性访问、列表推导式等等。

例如,可以调用一个内置函数或用标准或第三方模块导入的函数。

>>> # 运行echo命令
>>> import subprocess
>>> eval("subprocess.getoutput(&#39;echo Hello, World&#39;)")
&#39;Hello, World&#39;
>>> # 启动Firefox(如果有的话)
>>> eval("subprocess.getoutput(&#39;firefox&#39;)")
&#39;&#39;

在这个例子中,我们使用 Python 的 eval() 函数来执行一些操作系统命令。我们可以用这个功能做大量有用的事情。然而,eval()也会有一些严重的安全风险,比如允许一个恶意的用户在我们的机器中运行系统命令或任何任意的代码。

以上是怎麼使用Python eval函數的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除