不该用 eval 或 exec 处理用户输入,因其等同于开放 shell 权限;ast.literal_eval 是安全解析字面量的唯一内置方案;需计算表达式时应选用 simpleeval 等白名单控制库;强依赖动态代码时必须用子进程隔离并设资源限制。
eval 或 exec 执行用户输入直接调用 eval 或 exec 处理不可信输入等于开放 shell 权限——哪怕加了 timeout,也无法阻止无限循环、内存耗尽、os.system 调用或 __import__ 绕过限制。Python 没有沙箱机制,timeout 只能靠信号中断(Linux/macOS)或线程轮询(Windows),对 C 扩展阻塞、死循环无能为力。
ast.literal_eval 处理安全字面量如果只需要解析数字、字符串、元组、列表、字典、布尔和 None,ast.literal_eval 是唯一推荐的内置方案。它只解析 AST 中白名单节点,不执行任意代码。
ast.literal_eval("{'a': 1 + 2}") → 报 ValueError(含运算符,非法)ast.literal_eval("[1, 'hello', {'x': True}]") →
pyparsing 或 simpleeval 做白名单控制若必须支持 2 + 3 * x 这类简单算术,且 x 是可控变量,优先选专为安全求值设计的库:
simpleeval 默认禁用所有函数/属性访问,可通过 functions 和 names 显式注入(如只允许 math.sin 和变量 pi)simpleeval.SimpleEval().eval("2 ** 10") → 1024;但 .eval("__import__('os')") 直接抛 NameError
simpleeval 不处理超时,需外层用 concurrent.futures.ProcessPoolExecutor + timeout 包裹(避免线程无法中断 C 层阻塞)pyparsing 更底层,适合自定义语法规则,但开发成本高,仅建议复杂 DSL 场景exec + timeout?必须用子进程隔离若业务强依赖动态代码(如插件系统),唯一相对安全的做法是:在独立子进程中执行,设硬性资源限制,并用 timeout 控制生命周期。
threading.Timer 或 signal.alarm —— Windows 不支持 alarm,线程无法中断 exec 中的 C 扩展调用multiprocessing.Process 启动,配合 psutil.Process(memory_limit=...) 或 Linux prlimit 限制内存/CPUfrom multiprocessing import Process, Queue
def _unsafe_exec(code, result_q):
try:
namespace = {}
exec(code, namespace)
result_q.put(namespace.get('result'))
except Exception as e:
result_q.put(f"error: {e}")
q = Queue()
p = Process(target=_unsafe_exec, args=(user_code, q))
p.start()
p.join(timeout=2)
if p.is_alive():
p.terminate()
p.join()
子进程崩溃、OOM killer 干掉进程、甚至 exec 中的 os._exit(0) 都不会影响主程序——这是「尽量不 eval」失效后的最后一道防线,不是常规解法。