内置函数 exec() 能够执行字符串形式的Python 代码。它与 eval() 类似,但 exec() 不仅限于表达式,可以执行任意的 Python 语句。这使得 exec() 在某些动态编程场景中极为有用,尤其是在需要动态生成代码、脚本或构建自定义 DSL(领域特定语言)时。

然而,和 eval() 一样,exec() 也存在着极高的安全风险,因为它允许执行任意代码。

一、函数语法

exec(object, globals=None, locals=None)

参数:

object:可以是字符串,也可以是代码对象。若是字符串,它应包含有效的 Python 代码(如语句或表达式)。

globals:可选。指定全局命名空间,用于执行代码时的全局变量。默认为当前作用域。

locals:可选。输出结尾的字符,默认是换行符 '\n',可设置为空字符串或其他字符串。

返回值:

exec() 不返回任何值。它直接执行传入的代码,但不会有显式的返回结果。如果需要获取执行结果,可以通过执行代码的副作用(如修改变量)来访问。

二、基本用法举例

1、执行单一的 Python 语句

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

2、执行多个语句

code = """
a = 5
b = 10
result = a + b
"""
exec(code)
print(result)  # 输出:15

3、执行代码并使用指定的命名空间

global_scope = {}
local_scope = {"y": 5}

code = "result = y * 2"
exec(code, global_scope, local_scope)
print(local_scope["result"])  # 输出:10

4、动态定义函数

code = """
def greet(name):
    return 'Hello, ' + name
"""
exec(code)
print(greet("Alice"))  # 输出:Hello, Alice

三、典型应用场景

1、动态生成代码

exec() 可以在运行时动态地创建变量、函数、类等,适合用于需要动态构建代码的场景:

for i in range(3):
    exec(f"def func{i}(): print('This is function {i}')")

func0()  # 输出:This is function 0
func1()  # 输出:This is function 1
func2()  # 输出:This is function 2

2、自定义 DSL(领域特定语言

使用 exec() 可以实现简单的领域特定语言(DSL),使得用户可以动态输入代码并执行,类似于 SQL 或数学表达式的求值器。

# 自定义简单的数学表达式执行
expression = "3 * (5 + 2)"
exec(f"result = {expression}")
print(result)  # 输出:21

3、代码注入(危险)

尽管 exec() 可以让我们动态执行代码,但在面对不受信任的输入时,它极其危险。恶意的用户输入可能导致代码注入攻击。

# 用户输入(例如从网页表单)
user_input = "__import__('os').system('rm -rf /')"  # 恶意代码

exec(user_input)  # 会删除系统文件,造成灾难性后果

四、安全问题及替代方案

exec() 是非常强大的工具,但由于其执行任意 Python 代码的能力,它的安全风险非常大。尤其是在处理来自不受信任用户的输入时,绝对不能直接使用 exec()。

1、执行恶意代码

如果不加限制,攻击者可以通过注入恶意代码控制系统、窃取数据或执行其他有害操作。

2、数据泄露

exec() 执行代码时,可能会读取或修改系统中的敏感数据,造成隐私泄露。

3、文件操作

由于 exec() 能够调用 Python 的文件操作函数(如 open()、os.system() 等),恶意代码可以通过 exec() 执行文件删除、读取敏感文件等危险操作。

安全示例:限制执行环境

为了减少风险,可以使用如下方法限制执行的环境:

safe_globals = {"__builtins__": None}  # 禁用所有内建函数
code = "os.system('echo Hello World')"
exec(code, safe_globals)  # 将会引发 NameError,因为 os 未定义

然而,这种做法并不完全安全,exec() 仍然可以绕过这些限制执行恶意操作。因此,尽量避免使用 exec(),尤其是在处理来自不信任源的数据时。

4、替代方案

如果只是想动态执行某些操作,以下方法通常更为安全:

(1)使用 eval() 代替 exec()(仅适用于表达式计算,且需要严格验证输入)。

(2)使用 ast.literal_eval() 执行仅包含基本数据结构的字符串。

(3)使用专门的解析库(如 json.loads()、yaml.load() 等)来处理配置数据。

五、补充说明

1、避免使用 exec() 执行用户输入的代码。

2、限制执行环境,禁用内建函数和外部模块。

3、在需要动态执行代码时,考虑使用更安全的替代方案(如 eval()、ast.literal_eval())。

4、尽管 exec() 强大且灵活,但它的风险也意味着,只有在确实需要时,才应当考虑使用,并且要确保完全控制执行环境。

点赞有美意,赞赏是鼓励