Python 将字符串作为代码执行(实战指南)

Python 将字符串作为代码执行:从入门到实战

你有没有遇到过这样的场景?一段程序逻辑,本该是固定的,但需求突然变了,需要动态执行一段代码。比如,用户输入一个数学表达式,比如 2 * 3 + 1,你希望程序能自动计算结果,而不是手动写一堆 if-elif 判断。这时候,Python 就提供了非常强大的能力——将字符串作为代码执行

这听起来有点“魔法”,但其实它背后有明确的机制。今天我们就来深入探讨这个功能,从基础用法到安全风险,再到实际应用场景,一步步带你掌握这一强大技巧。


什么是 Python 将字符串作为代码执行?

简单来说,就是让 Python 解释器把一段字符串当成真正的 Python 代码来运行。比如,你有一个字符串:

code_str = "print('Hello, World!')"

你希望这段字符串不是作为普通文本,而是真正“执行”一遍。Python 提供了两个核心函数来实现这个功能:eval()exec()

📌 关键点:它们都允许你动态执行代码,但用途略有不同。

eval():只处理表达式,返回结果

eval() 用于计算一个字符串表达式,并返回结果。它只接受“表达式”,不能处理复杂的语句,比如赋值、循环等。

expression = "2 + 3 * 4"
result = eval(expression)
print(result)  # 输出:14

✅ 注释:这里的 expression 是一个字符串,包含数学表达式。eval() 会解析并计算它,返回数值 14

exec():执行任意代码块,无返回值

exec() 可以执行任意的 Python 代码,包括函数定义、变量赋值、循环等,但它本身不返回值(返回 None)。

code_block = """
name = 'Alice'
age = 25
print(f'Hello, {name}, you are {age} years old.')
"""

exec(code_block)

✅ 注释:exec() 执行了整个代码块,包括变量赋值和打印语句。注意,它不会返回任何值。


eval() 与 exec() 的区别:一图胜千言

特性 eval() exec()
可执行内容 表达式(如:2 + 3 任意代码(如:iffordef
返回值 有,返回表达式结果 无,返回 None
安全性 相对较高(只能表达式) 较低(可执行任意命令)
适用场景 数学计算、配置表达式解析 动态脚本执行、插件系统

📌 形象比喻eval() 就像一个计算器,只能算加减乘除;而 exec() 更像一个微型 Python 解释器,能运行整个程序。


实际案例:实现一个简易计算器

我们来做一个小项目,让用户输入数学表达式,程序自动计算结果。这正是“Python 将字符串作为代码执行”的经典应用。

def simple_calculator():
    print("欢迎使用简易计算器!输入表达式(如:2 + 3 * 4),输入 'quit' 退出。")
    
    while True:
        user_input = input("请输入表达式:").strip()
        
        # 退出条件
        if user_input.lower() == 'quit':
            print("再见!")
            break
        
        try:
            # 使用 eval 执行表达式
            result = eval(user_input)
            print(f"结果:{result}")
        except Exception as e:
            print(f"错误:{e}")

simple_calculator()

✅ 注释:这段代码通过 eval(user_input) 将用户输入的字符串作为表达式执行。比如输入 10 / 2 + 5,会输出 10.0。注意:我们用了 try-except 捕获异常,防止非法输入导致程序崩溃。


安全风险:别让“魔法”变成“炸弹”

虽然 eval()exec() 很方便,但它们也潜藏着巨大的安全风险。如果用户输入的字符串包含恶意代码,后果不堪设想。

比如,以下输入会删除你的系统文件(在某些环境下):

evil_code = "__import__('os').system('rm -rf /')"
eval(evil_code)

⚠️ 警告:在生产环境中,绝对不能直接对用户输入使用 eval()exec(),除非你完全信任输入来源。

如何安全地使用?

1. 限制允许的变量和函数

你可以通过传入 globalslocals 参数,控制执行环境。

safe_globals = {
    '__builtins__': {},  # 禁用内置函数
    'abs': abs,
    'min': min,
    'max': max,
}

safe_locals = {
    'x': 10,
    'y': 20,
}

expression = "x * 2 + y"
result = eval(expression, safe_globals, safe_locals)
print(result)  # 输出:40

✅ 注释:我们通过 safe_globals 限制了可用的内置函数,只保留 absminmax。这样即使用户输入 __import__('os').system('ls'),也会因为 __builtins__ 被清空而报错。

2. 使用 ast.literal_eval() 替代

如果你只是想解析字符串中的字面量(如列表、字典、数字),推荐使用 ast.literal_eval(),它只支持安全的字面量结构。

import ast

data_str = "[1, 2, 3, 4]"
data = ast.literal_eval(data_str)
print(data)  # 输出:[1, 2, 3, 4]
print(type(data))  # 输出:<class 'list'>

try:
    ast.literal_eval("os.system('rm -rf /')")
except Exception as e:
    print("解析失败:", e)  # 输出:解析失败: malformed node or string

✅ 注释:ast.literal_eval() 只能解析 Python 中的“字面量”语法,不会执行任何代码,是处理配置、JSON-like 数据的首选。


高级应用:动态代码生成与插件系统

在一些高级应用中,比如框架、脚本引擎或自动化测试工具,exec() 被用来动态加载和执行用户自定义的代码。

案例:自定义函数注册器

registered_functions = {}

def register_function(name, code_str):
    """将字符串代码注册为函数"""
    # 将字符串代码包装成函数定义
    full_code = f"def {name}():\n    {code_str}"
    exec(full_code, registered_functions)  # 注册到命名空间
    print(f"函数 {name} 已注册。")

def call_function(name):
    """调用已注册的函数"""
    if name in registered_functions:
        registered_functions[name]()
    else:
        print(f"函数 {name} 未找到。")

register_function("greet", "print('你好,世界!')")
register_function("add", "print(2 + 3)")

call_function("greet")  # 输出:你好,世界!
call_function("add")   # 输出:5

✅ 注释:我们通过 exec() 将字符串代码动态生成为函数,并注册到 registered_functions 字典中。这种方式在插件系统中非常常见。


总结:掌握 Python 将字符串作为代码执行的正确姿势

“Python 将字符串作为代码执行”是一个强大但危险的功能。它让程序具备了动态适应的能力,但在使用时必须格外谨慎。

  • eval() 执行表达式,适合数学计算、配置解析;
  • exec() 执行代码块,适合动态脚本、插件机制;
  • 切勿对用户输入直接使用,除非你完全控制输入来源;
  • 推荐使用 ast.literal_eval() 处理字面量数据;
  • 通过限制 globalslocals 来创建安全执行环境。

记住:能用的,不一定该用。真正成熟的开发者,不是靠“魔法”解决问题,而是用最安全的方式达成目标。

当你下次遇到“需要执行一段动态代码”的需求时,不妨先问自己一句:有没有更安全的替代方案?答案往往是肯定的。


附录:常见问题速查

问题 解答
eval() 可以执行 print() 吗? 可以,但 print 必须在允许的命名空间中。如果 __builtins__ 被禁用,则无法使用。
exec() 会返回值吗? 不会,返回 None
ast.literal_eval()eval() 有什么区别? ast.literal_eval() 只能解析字面量,不会执行代码,更安全。
能否在 eval() 中使用 import 默认情况下不行,因为 __builtins__ 被禁用。若想启用,需显式允许。

最后提醒:在你的项目中,每次使用 eval()exec() 前,请确认输入来源是否可信。安全永远比“方便”更重要。