Python 的 exec 与 eval :动态代码执行的能力、风险与工程实践

Python 是一门极其灵活的语言,而 execeval 则是这种灵活性最极端的体现------它们能让你在运行时把一段字符串当作代码来执行。这种能力听起来很酷,用起来却像在玩火。本文从原理出发,深入剖析这两个函数的本质差异、潜藏的安全地雷,以及在真实工程中如何做出理智的取舍。


一、它们到底是什么?

evalexec 都是 Python 的内置函数,但定位截然不同。

eval 专门用于求值表达式,它接收一个字符串,把它当作一个 Python 表达式来计算,并返回结果:

python 复制代码
result = eval("1 + 2 * 3")
print(result)  # 输出 7

exec 则更"野",它可以执行任意的 Python 代码块 ,包括函数定义、循环、赋值等,但不返回值(返回 None):

python 复制代码
code = """
def greet(name):
    return f"Hello, {name}!"
"""
exec(code)
print(greet("World"))  # 输出 Hello, World!

两者的核心区别可以这样理解:eval 是一个计算器 ,只能算出一个值;exec 是一个解释器,能跑完整的程序。


二、底层原理:Python 其实在偷偷编译

很多人以为 exec / eval 是"直接解释字符串",其实不然。CPython 在执行这两个函数时,会先把字符串编译成字节码(bytecode) ,再交给虚拟机执行------这和正常 import 一个模块的流程本质上是一样的。

这意味着两件事:

  • 性能损耗是真实存在的 :每次调用都要经历编译,比直接调用函数慢得多。如果在循环里反复 eval,性能会很难看。
  • compile() 可以预编译 :如果你确实需要反复执行同一段动态代码,可以先用 compile() 生成 code object,再传给 exec,避免重复编译的开销。

三、安全噩梦:为什么说它们是"装了子弹的枪"

这是最关键的部分。evalexec 的危险程度,远超大多数开发者的想象。

3.1 任意代码执行(RCE)

假设你写了一个"在线计算器",用 eval 来处理用户输入:

python 复制代码
# 危险!永远不要这样做
user_input = input("请输入表达式:")
result = eval(user_input)

用户只需要输入:

python 复制代码
__import__('os').system('rm -rf /')

你的服务器就可能被清空。这就是远程代码执行(RCE) 漏洞------攻击者可以通过精心构造的字符串,在你的服务器上执行任意系统命令。

3.2 "沙箱"是假的

很多人会想到"限制命名空间"来防御:

python 复制代码
# 看起来很安全?其实不是
eval(user_input, {"__builtins__": {}})

但这种防御形同虚设。Python 对象系统极其复杂,攻击者可以通过对象的 __class____subclasses____globals__ 等魔术属性,绕过任何手工构造的沙箱,最终找到通往系统调用的路径。

一个经典的绕过姿势:

python 复制代码
# 即使 builtins 被清空,也能找到 os 模块
eval("().__class__.__bases__[0].__subclasses__()")

这会列出所有子类,其中往往藏着可以访问文件系统的类。

3.3 风险全景图


四、更安全的替代方案

在大多数场景下,你根本不需要 evalexec,换个思路就能解决问题。

4.1 ast.literal_eval:只解析数据,不执行代码

如果你只是想把一个字符串形式的 Python 字面量(数字、列表、字典等)转换成对象,用 ast.literal_eval 就够了,它只允许解析安全的字面量,拒绝任何可执行代码:

python 复制代码
import ast

# 安全:只解析字面量
data = ast.literal_eval("{'name': 'Alice', 'age': 30}")
print(data)  # {'name': 'Alice', 'age': 30}

# 会抛出异常,而不是执行
ast.literal_eval("__import__('os').system('ls')")  # ValueError!

4.2 用字典替代动态变量名

Reddit 上那个游戏开发的例子很典型------用 eval(weaponName + ".get_stat()") 来动态访问对象。正确的做法是用字典:

python 复制代码
# 不好的写法
thing = eval(nameOfWeapon + ".get_itemStat()")

# 好的写法:用字典存储对象
weapons = {
    "sword": Sword(),
    "bow": Bow(),
}
thing = weapons[nameOfWeapon].get_itemStat()

4.3 getattr:动态访问属性

需要动态访问对象属性时,getattr 是正确选择:

python 复制代码
# 不好的写法
eval(f"obj.{method_name}()")

# 好的写法
getattr(obj, method_name)()

4.4 替代方案对比

场景 危险做法 安全替代
解析数据字符串 eval("{'a': 1}") ast.literal_eval(...)
动态访问属性 eval(f"obj.{attr}") getattr(obj, attr)
动态调用方法 eval(f"obj.{method}()") getattr(obj, method)()
按名称获取对象 eval(name) dict[name]getattr(module, name)
解析 JSON eval(json_str) json.loads(json_str)

CITE_2


五、工程实践:什么时候可以用,怎么用

execeval 并非一无是处,在某些特定场景下它们是合理的工具。关键在于:你是否完全控制了输入来源

5.1 合理的使用场景

  • REPL / 交互式解释器 :Jupyter Notebook、IPython 本质上就是在用 exec 执行用户输入的代码,但用户就是自己,风险可控。
  • 代码生成工具:元编程、ORM 框架在内部动态生成类或方法时,输入完全由框架自身控制。
  • 配置文件执行 :某些框架(如 web2py)用 exec 加载配置,但这些配置文件来自可信的本地文件系统。
  • 测试框架pytest 等工具在内部用 exec 动态执行测试代码。

5.2 工程中的决策树

5.3 如果非用不可,这样做

python 复制代码
import traceback

def safe_exec(code: str, allowed_globals: dict = None):
    """
    一个相对安全的 exec 封装(仅适用于可信输入)
    """
    # 1. 限制全局命名空间,只暴露必要的内容
    safe_globals = {
        "__builtins__": {
            "print": print,
            "range": range,
            "len": len,
            # 只暴露你真正需要的内置函数
        }
    }
    if allowed_globals:
        safe_globals.update(allowed_globals)
    
    # 2. 捕获所有异常,避免崩溃
    try:
        exec(compile(code, "<string>", "exec"), safe_globals)
    except Exception as e:
        print(f"执行失败:{traceback.format_exc()}")

即便如此,这种"安全封装"也只适用于可信输入,对于真正的外部用户输入,没有任何手工沙箱是可靠的。CITE_4


六、总结

evalexec 是 Python 动态性的极致体现,但它们更像是语言留给框架开发者和工具作者的"后门",而非普通业务代码的常规工具。

一句话概括工程原则:如果输入来自用户,永远不要碰 eval/exec;如果输入来自自己,先想想有没有更简单的替代方案。 大多数时候,ast.literal_evalgetattr、字典映射就已经够用了。真正需要动态执行代码的场景,往往意味着你在构建一个解释器或框架------那时候,你自然会知道自己在做什么。CITE_2CITE_4


参考来源

相关推荐
user-猴子1 小时前
从零构建 2048 游戏,解析“Python-Use”范式的完整闭环
开发语言·python·游戏
Conan在掘金1 小时前
鸿蒙 ArkUI V2 装饰器:AppStorageV2,应用级状态存取,告别 V1 的「set/get 手动同步」
后端
咕白m6251 小时前
通过 C++ 写入数据到 Excel 文档
c++·后端
郝学胜_神的一滴1 小时前
Python 高级编程 025:二分利器bisect模块:优雅维系有序序列,极致优化检索性能
python·pycharm
极光技术熊1 小时前
AI应用开发中的流式输出:从协议原理到工程实战的完整指南
后端·架构
xn71331 小时前
AI SDK 7 迁移实战:TypeScript 通过后,生产环境还会坏在哪里?
vue.js·人工智能·后端
Lcos1 小时前
我顺手跑了个 go test,结果跑出了 panic
后端
程序员David1 小时前
雪花 ID + MyBatis = 隐式转 Double 撞键?我排查了一整天的隐蔽坑
后端
星栈独行1 小时前
Node 接口该写同步还是异步?
服务器·开发语言·后端·程序人生·node.js