Python中的eval() & exec()

eval()

eval() 是一个内置的 Python 函数,它允许执行一个字符串作为 Python 表达式并返回结果。换句话说,它可以从字符串中动态地执行 Python 表达式。

eval 函数的完整签名是:

eval(expression, globals=None, locals=None)
  • expression: 必须的参数。这是要计算的 Python 表达式,以字符串形式给出。
  • globals: 可选的参数。这是用于提供全局命名空间(即全局变量)的字典。在上面的例子中,它被设置为 None,这意味着它将使用当前的全局命名空间。
  • locals: 可选的参数。这是用于提供局部命名空间(即局部变量)的字典。

例如:

local_vars = {'a': 5, 'b': 3}
result = eval("a + b", None, local_vars)
print(result)  # 输出:8

在这个例子中,在表达式 eval("a + b", None, local_vars) 中,eval 函数用于计算一个字符串表达式并返回其结果。此表达式尝试对变量 ab 进行加法操作。ab 的值(分别为 5 和 3)是从 local_vars 字典中获取的,并且它们的和(8)被返回。

  • "a + b": 这是要计算的表达式。
  • None: 这表示不使用任何特定的全局命名空间,而是使用当前的全局命名空间。
  • local_vars: 这是一个开发者提供的字典,包含局部变量。函数将尝试在这个字典中查找 ab 的值。如果这个字典确实包含了 ab 的值,那么它们的值将被用于加法操作【1】。

基本用法:

result = eval("5 + 3")
print(result)  # 输出: 8

可以处理复杂的表达式:

x = 10
result = eval("x * 3 + 2")
print(result)  # 输出: 32

可以使用本地和全局命名空间:

这意味着我们可以在 eval() 内部访问和修改变量。

local_vars = {"a": 5, "b": 3}
result = eval("a + b", None, local_vars)
print(result)  # 输出: 8

安全性问题:

使用 eval() 需要谨慎,因为它允许执行任意代码。这可能会导致安全风险,特别是当我们从不受信任的来源获取字符串并用 eval() 执行时。例如:

# 永远不要这样做!
dangerous_string = "os.system('rm -rf /')"  # 这将删除你的文件系统!
eval(dangerous_string)  

为了避免潜在的安全风险,不要使用 eval() 除非我们完全信任源,并且确信没有更好的方法来达到同样的目的。

exec() 的区别:

  • eval() 只能计算单个表达式并返回值,而 exec() 可以执行更复杂的 Python 代码块,但不返回任何值。
x = 10
eval("x = x + 5")  # 这会引发语法错误,因为赋值不是一个表达式

exec("x = x + 5")  # 这是有效的

总之,尽管 eval() 是一个强大的工具,但它必须小心且明智地使用,尤其要注意其安全性问题。

exec()

exec() 是 Python 的一个内置函数,用于动态地执行 Python 程序,可以是一个字符串或对象代码(例如由 compile() 生成的代码对象)。这使得可以在运行时动态地生成和执行代码。与 eval() 不同,exec() 可以执行更复杂的代码块(如多行代码、声明和控制流语句),而不仅仅是单一的表达式。

exec() 的函数签名如下:

exec(source, globals=None, locals=None)
  • source:必需参数。表示要执行的 Python 代码。它可以是普通的字符串、字节字符串,或者是由 compile() 函数返回的代码对象。

  • globals:可选参数。表示全局命名空间(变量)的字典。如果提供了此参数,则在执行代码时会使用这个字典作为全局命名空间。否则,exec() 会使用当前全局命名空间。

  • locals:可选参数。表示局部命名空间(变量)的字典。如果提供了此参数,则在执行代码时会使用这个字典作为局部命名空间。如果没有提供,它会使用 globals 字典(这意味着,如果只提供 globals,那么它将同时用作全局和局部命名空间)。

关于 globalslocals 的一些注意事项:

  1. 如果 exec() 只提供了 source,不指定 globalslocals,那么它将在当前全局和局部命名空间中执行。
  2. 如果提供了 globals 字典,但没有提供 locals 字典,那么 globals 会同时作为全局和局部命名空间。
  3. 如果同时提供了 globalslocals 字典,那么它们分别会被用作各自的命名空间。
  4. 尽管 globalslocals 的名字可能让人觉得它们只能用于存储变量,但实际上它们可以包含函数、类等任何 Python 对象。

例子:

x = 10

def test_exec(source, g=None, l=None):
    exec(source, g, l)

global_dict = {'x': 20}
local_dict = {'x': 30}

# 使用默认的全局和局部命名空间
test_exec('print(x)')  # 输出: 10

# 使用指定的全局命名空间
test_exec('print(x)', global_dict)  # 输出: 20

# 使用指定的全局和局部命名空间
test_exec('print(x)', global_dict, local_dict)  # 输出: 30

请注意,由于 exec() 可以执行任意代码,因此必须谨慎使用它,以避免潜在的安全风险。特别是当执行的代码来源于不受信任的来源时。

基本用法:

可以使用 exec() 执行字符串中的任何有效的 Python 代码:

code = """
x = 10
y = 20
print(x + y)
"""
exec(code)  # 输出: 30

作用域:

默认情况下,exec() 将在调用它的当前命名空间(即本地和全局变量)中执行。但我们可以为其提供单独的命名空间:

globals_var = {"a": 10}
locals_var = {"b": 20}
code = "result = a + b"

exec(code, globals_var, locals_var)
print(locals_var["result"])  # 输出: 30

安全性:

eval() 一样,exec() 也带有安全风险。它可以执行任意的 Python 代码,这意味着恶意代码可以导致数据泄露、数据损坏或其他安全隐患。因此,使用 exec() 时必须非常小心,尤其是当我们执行的代码来自不受信任的来源时。

返回值:

exec() 不返回任何值(即它的返回值是 None)。它只是执行给定的代码。

eval() 的对比:

  • eval() 用于计算单个表达式并返回其值。
  • exec() 用于执行任意的代码块(无论其复杂度如何)并不返回任何值。

示例:

使用 exec() 动态地定义和调用函数:

code = """
def greet(name):
    return f"Hello, {name}!"
"""

exec(code)

# 使用 exec 中定义的函数
greeting = greet("Alice")
print(greeting)  # 输出: Hello, Alice!

总的来说,exec() 是一个强大的函数,允许我们动态地执行 Python 代码。然而,由于潜在的安全风险,建议在非常受限制和受控制的环境中使用它,并尽量避免执行不受信任的代码。


注【1】 如果 local_vars 字典不包含 ab 的值,eval 函数会产生一个 NameError

例如:

local_vars = {'a': 5}
result = eval("a + b", None, local_vars)

上述代码将抛出以下异常:

NameError: name 'b' is not defined

这是因为在提供的 local_vars 字典中,b 的值没有被定义。

但是,值得注意的是,如果 ab 既不在 local_vars 中,也不在当前的全局命名空间中,则会抛出这个异常。如果它们在当前的全局命名空间中有定义,那么这些全局值将被使用。

例如:

a = 10
b = 20
local_vars = {'a': 5}
result = eval("a + b", None, local_vars)
print(result)  # 输出:25

在这个例子中,尽管 b 没有在 local_vars 字典中定义,但它在全局命名空间中有定义,所以其值 20 被使用了。而 a 的值从 local_vars 中取得,即 5。所以最终的结果是 5 + 20 = 25


在 Python 中,每个代码块(如函数、模块、类等)都有其自己的命名空间。这个命名空间是一个存储所有在该代码块中定义的变量、函数、类等标识符的字典。当我们在代码中引用一个变量或函数名时,Python 会查找这个命名空间来获取它的值。

  • 全局命名空间:这是与特定模块相关的命名空间。当我们在模块级别定义变量或函数时(即不在任何函数或类内部),它们被添加到这个全局命名空间。在模块内,可以通过内置的 globals() 函数来获取这个命名空间的字典。

  • 局部命名空间:当我们在一个函数或方法内部定义变量或函数时,它们被添加到局部命名空间。局部命名空间仅在其相应的函数或方法内部可用,并在函数执行完毕后被销毁。在函数内,可以通过内置的 locals() 函数来获取这个命名空间的字典。

例如:

x = 10  # 这个变量在全局命名空间

def some_function():
    y = 5  # 这个变量在 some_function 的局部命名空间
    print("Locals:", locals())

some_function()
print("Globals:", globals())

在这个例子中:

  • x 是全局变量,因为它是在模块级别定义的,所以它在全局命名空间中。
  • y 是局部变量,因为它是在 some_function 内部定义的,所以它在这个函数的局部命名空间中。

当我们说“默认的全局和局部命名空间”时,我们是指:

  • 在函数内部,如果没有指定其他的命名空间,局部命名空间就是该函数的命名空间,而全局命名空间是模块的命名空间。

  • 在模块级别(即不在任何函数内部),全局命名空间和局部命名空间实际上是相同的,都是模块的命名空间。

这种分层的命名空间结构使得变量的查找和作用域的管理变得明确和灵活。当在一个函数内部引用一个变量时,Python 首先会查找局部命名空间。如果没有找到,它会继续在全局命名空间中查找,然后继续在更高级的命名空间中查找,直到达到内置命名空间。


有关Python中的命名空间详情请参考 Python中的命名空间

你可能感兴趣的:(Python,python)