如何快速执行字符串中的Python代码?百万次执行求eval()更快替代法
1. 如何快速评估字符串中的Python代码?
如果是单次或少量执行场景,eval()和exec()是最直接的工具,但要想提速,预编译代码对象是核心技巧——直接用eval()传入字符串时,每次都要重复解析、编译字符串为字节码,而预编译能把这一步只做一次,后续执行直接复用编译后的结果。
举个实际例子:
# 直接使用eval(每次执行都要重新编译字符串) result = eval("1 + 2 * 3") # 预编译后执行(仅编译一次,多次执行效率更高) code_obj = compile("1 + 2 * 3", "<string>", "eval") result = eval(code_obj)
compile()的三个参数分别是:待编译的字符串、代码来源标识(一般填<string>即可)、模式("eval"对应表达式,"exec"对应语句块,"single"对应单行交互语句)。根据你的代码类型选对模式,能避免不必要的性能开销。
2. 百万次执行场景下,比eval()更快的替代方案
你提到eval()单次耗时100微秒,而嵌入代码仅8微秒,差距主要来自eval()每次的编译开销和作用域查找损耗。针对百万次执行的高频场景,推荐以下几种方案:
方案一:预编译代码对象并重复执行
如上文所说,先把字符串编译成代码对象,之后每次执行直接调用这个对象,能省去重复编译字符串的时间。实测下来,这种方法能把单次执行时间降到20-30微秒左右(具体取决于代码复杂度)。
示例:
code_str = "x * 2 + y" # 预编译为eval模式的代码对象 code_obj = compile(code_str, "<string>", "eval") # 百万次执行循环 for _ in range(1_000_000): result = eval(code_obj, {"x": 5, "y": 3})
方案二:动态生成可调用函数
如果你的字符串代码是表达式或可封装成函数的逻辑,把它转换成原生Python函数,执行速度会无限接近你嵌入代码的8微秒级别。这里有两种实现方式:
方法A:用types.FunctionType创建函数
import types code_str = "x + y * 2" # 编译成eval模式的代码对象 code_obj = compile(code_str, "<string>", "eval") # 从代码对象中提取表达式逻辑,生成带参数的函数 func = types.FunctionType(code_obj.co_consts[0], globals(), "calc_func", argdefs=(0, 0)) # 调用函数,速度和原生定义的函数几乎一致 for _ in range(1_000_000): result = func(5, 3)
方法B:用exec()动态定义函数
如果你的代码逻辑更复杂(比如多行语句),直接用exec()定义函数会更直观:
code_str = """ def calc_func(x, y): temp = y * 2 return x + temp """ # 执行字符串代码,将函数定义到当前作用域 exec(code_str) # 百万次调用,性能和嵌入代码无差别 for _ in range(1_000_000): result = calc_func(5, 3)
方案三:针对数值计算的JIT编译(可选)
如果你的字符串代码是纯数值计算逻辑,可以用numba做即时编译,进一步压榨性能。不过需要确保代码符合numba的兼容要求(比如避免使用复杂Python对象):
from numba import jit # 先通过exec动态定义函数 exec("def calc_func(x, y): return x + y * 2") # 用numba的JIT编译优化 jit_func = jit(nopython=True)(calc_func) # 百万次执行,速度可能比原生Python函数还快 for _ in range(1_000_000): result = jit_func(5, 3)
关键安全提醒
不管用哪种方法,如果字符串代码来自不可信的外部输入,绝对不要使用eval()、exec()或动态函数生成——这些方法会执行任意Python代码,存在极高的安全风险。
内容的提问来源于stack exchange,提问作者user12087854

