掌握Python eval()函数:解析动态代码执行的神奇之处

eval() 是 Python 提供的一个内置函数,用于动态执行字符串形式的表达式

虽然它功能强大,但同时也伴随着一定的风险和局限性。在本文中,我们将详细介绍 eval() 的用法、注意事项以及一些常见的应用场景。

一、eval() 函数的基本用法

![[内置函数eval()-20240620152417093.webp]]

基本语法:

eval(expression, globals=None, locals=None)
  • expression(必需):一个字符串,包含需要被解析并执行的有效 Python 表达式。
  • globals(可选):一个字典,指定全局命名空间,默认使用当前全局命名空间。
  • locals(可选):一个字典,指定局部命名空间,默认使用当前局部命名空间。

示例:

# 基本用法
result = eval('2 + 3')
print(result)  # 输出 5

# 使用全局和局部命名空间
x = 1
result = eval('x + 1', {'x': 10})
print(result)  # 输出 11

# 使用局部命名空间
result = eval('x + y', {'x': 1}, {'y': 2})
print(result)  # 输出 3

注意:你也可以使用 exec() 来动态执行 Python 代码。eval()exec() 的主要区别在于 eval() 只能执行或评估表达式,而 exec() 可以执行任何 Python 代码。

1.1 参数expression(必需)

eval() 的第一个参数称为 expression, 包含需要被解析并执行的有效 Python 表达式。

运行时,eval() 执行以下步骤:

  1. 解析 expression
  2. 将其编译为字节码
  3. 将其作为 Python 表达式进行评估
  4. 返回评估结果

如果将字符串传给eval()时,函数会返回执行字符串得到的值:

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

默认情况下,eval() 可以访问全局名称,例如上面示例中的 x。

expression可以是字符串表达式或者代码对象,但不能是复合语句、赋值操作或者非完整的表达式。

expression 可接受字符串形式的表达式

对于 eval() 的第一个参数的命名——expression (表达式), 强调了该函数仅适用于表达式,而不适用于复合语句

Python 文档对表达式(expression) 和语句(Statement)的定义如下:

expression
A piece of syntax which can be evaluated to some value. In other words, an expression is an accumulation of expression elements like literals, names, attribute access, operators or function calls which all return a value. In contrast to many other languages, not all language constructs are expressions. There are also statements which cannot be used as expressions, such as while. Assignments are also statements, not expressions. (Source)

statement
A statement is part of a suite (a “block” of code). A statement is either an expression or one of several constructs with a keyword, such as if, while or for. (Source)

expression不接受复合语句

如果传入一个复合语句给 eval(),那么会出现 SyntaxError。看看以下的示例,其中尝试使用 eval() 执行一个 if 语句:

>>> x = 100
>>> eval("if x: print(x)")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    if x: print(x)
    ^
SyntaxError: invalid syntax
expression不接受赋值操作

eval() 也不允许赋值操作, 因为赋值操作是语句而不是表达式。

>>> eval("pi = 3.1416")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    pi = 3.1416
       ^
SyntaxError: invalid syntax
expression不接受不完整表达式

同样地,如果解析器无法理解输入的表达式,也会导致 SyntaxError。下面的例子就是一个解析器无法理解的“不完整”表达式。

>>> # Incomplete expression
>>> eval("5 + 7 *")
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    eval("5 + 7 *")
  File "<string>", line 1
    5 + 7 *
          ^
SyntaxError: unexpected EOF while parsing
expression 可接受代码对象

可以使用 compile() 函数,将输入的字符串编译为代码对象或 AST 对象,再传入eval()执行

compile() 的基本语法:compile(source, filename, mode)
其中

  • source 要编译的源代码,可以是普通字符串、字节字符串或者 AST 对象。
  • filename 源代码所在的文件名。如果源代码来自字符串输入,通常设为 <string>
  • mode参数指定编译后的代码类型,常用的是 “exec”(用于执行代码)和 “eval”(用于评估表达式)。
>>> # Arithmetic operations
>>> code = compile("5 + 4", "<string>", "eval")
>>> eval(code)
9
>>> code = compile("(5 + 7) * 2", "<string>", "eval")
>>> eval(code)
24
>>> import math
>>> # Volume of a sphere
>>> code = compile("4 / 3 * math.pi * math.pow(25, 3)", "<string>", "eval")
>>> eval(code)
65449.84694978735

1.2 参数globals(可选)

eval() 的第二个参数称为 globals。它是可选的,是一个字典,用于为 eval() 提供全局命名空间。传递给 globals 的所有名称将在执行时供 eval() 使用。

全局名称是指在当前全局作用域或命名空间中可用的所有名称,可以在代码的任何地方访问它们。

默认状态

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

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

在上面的示例中, x 和 y包含在当前全局作用域中的全局变量。

自定义globals字典

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

如果向 globals 参数提供了一个自定义字典,那么 eval() 将只会使用该字典中的名称作为全局变量。任何在这个自定义字典之外定义的全局名称在 eval() 内部将无法访问。这就是为什么在上面的代码中尝试访问 y 时,Python 抛出 NameError 的原因:传递给 globals 的字典不包括 y。

你可以通过在字典中列出名称来将名称插入到 globals 中,这样这些名称在评估过程中将可用。例如,如果将 y 插入到 globals 中,那么上面示例中的 “x + y” 的评估将按预期工作

为了让 “x + y” 按预期工作, 可以把y加入自定义 globals 字典中,如下所示:

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

此外,还可以提供当前全局作用域中不存在的名称。 z在当前全局作用域中未定义, 为了实现它,需要为z提供一个具体的值。

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

在这种情况下,eval() 能够访问 z,就像它是一个全局变量一样。

globals 背后的机制非常灵活。你可以将任何可见的变量(全局的、局部的或非局部的)传递给 globals。你还可以像上面的示例中那样传递自定义的键值对,例如 {“z”: 300}。eval() 将把它们都视为全局变量。

1.3 参数locals

locals 是另一个可选参数,用于保存一个字典。在这种情况下,这个字典包含 eval() 使用的局部变量名。

局部变量是指那些在给定函数内部定义的名称(变量、函数、类等)。局部变量只能从封闭的函数内部访问。当你编写函数时,可以定义这些类型的名称。

由于 eval() 已经被编写好,你无法向它的代码或局部作用域中添加局部名称。然而,你可以向 locals 参数传递一个字典,eval() 将把这些名称视为局部变量:

>>> eval("x + 100", {}, {"x": 100})
200
>>> eval("x + y", {}, {"x": 100})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'y' is not defined

第一个调用 eval() 中的第二个字典包含变量 x。这个变量被 eval() 解释为局部变量。换句话说,它被视为在 eval() 主体内定义的变量。

可以在表达式中使用 , 但是使用 y会返回NameError,因为 y 在全局命名空间或局部命名空间中都没有定义。

与 globals 类似,可以将任何可见的变量(全局的、局部的或非局部的)传递给 locals。也可以传递自定义的键值对,例如 {“x”: 100}。

注意:向locals 提供字典的前提是——需要向 globals 提供一个字典。

因为eval() 不接受关键字参数,如果使用关键字参数就会得到TypeError 错误,如下:

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

因此,在向 locals 提供字典之前,你需要先向 globals 提供一个字典。

如果不向 locals 传递字典,则默认为传递给 globals 的字典。以下是一个示例,你向 globals 传递了一个空字典,并且没有向 locals 传递任何内容:

>>> x = 100
>>> eval("x + 100", {})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'x' is not defined

由于没有向 locals 提供自定义字典,参数默认为传递给 globals 的字典。在这种情况下,eval() 无法访问 x,因为 globals 是一个空字典。

globals 和 locals 的区别

在Python中,globals和locals之间的主要实际区别在于:

  • Globals(全局变量):无论是否提供自定义字典,Python都会自动向globals字典中插入一个 “builtins” 键。这确保了eval()可以访问所有Python内置的函数和对象。
  • Locals(局部变量):如果你向locals提供了自定义字典,在eval()执行期间这个字典将保持不变。在eval()内部,任何你在这个字典中定义的变量或函数都将作为局部名称使用,并且不会受到eval()外部的更改影响。
    简而言之,globals会被Python自动添加一个 “builtins” 键,而locals则保持你提供的静态字典不变。

*动态执行 VS 静态执行

下面两个result都能得到相同的结果(即 result 的值都是 4),但是它们有什么不同呢?

# 使用eval
result = eval("2 + 2")

# 不使用eval
result = 2 + 2

result = eval(“2 + 2”)

  • 动态执行eval 函数接受一个字符串参数,并将这个字符串当作 Python 表达式来执行。在执行时,字符串 "2 + 2" 被解析为一个数学表达式并计算其结果。
  • 灵活性:由于 eval 可以处理任意的字符串表达式,它提供了动态执行代码的能力。例如,可以从用户输入或文件中读取表达式并执行。
  • 风险eval 存在安全风险,因为它可以执行任意代码。如果传入的字符串包含恶意代码,可能会导致安全漏洞。

result = 2 + 2

  • 静态执行:这里的表达式是直接在代码中写明的,Python 在编译时就知道 2 + 2 是一个数学表达式,并会直接计算其结果。
  • 性能:静态表达式的执行效率通常比 eval 高,因为不需要解析字符串。
  • 安全性:这种方式不存在安全风险,因为所有的代码都是明确写在代码文件中的,不会执行任何外部或不受信任的代码。

参考链接: Python eval(): Evaluate Expressions Dynamically – Real Python

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值