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) |
任意代码(如:if、for、def) |
| 返回值 | 有,返回表达式结果 | 无,返回 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. 限制允许的变量和函数
你可以通过传入 globals 和 locals 参数,控制执行环境。
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限制了可用的内置函数,只保留abs、min、max。这样即使用户输入__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()处理字面量数据; - 通过限制
globals和locals来创建安全执行环境。
记住:能用的,不一定该用。真正成熟的开发者,不是靠“魔法”解决问题,而是用最安全的方式达成目标。
当你下次遇到“需要执行一段动态代码”的需求时,不妨先问自己一句:有没有更安全的替代方案?答案往往是肯定的。
附录:常见问题速查
| 问题 | 解答 |
|---|---|
eval() 可以执行 print() 吗? |
可以,但 print 必须在允许的命名空间中。如果 __builtins__ 被禁用,则无法使用。 |
exec() 会返回值吗? |
不会,返回 None。 |
ast.literal_eval() 和 eval() 有什么区别? |
ast.literal_eval() 只能解析字面量,不会执行代码,更安全。 |
能否在 eval() 中使用 import? |
默认情况下不行,因为 __builtins__ 被禁用。若想启用,需显式允许。 |
最后提醒:在你的项目中,每次使用
eval()或exec()前,请确认输入来源是否可信。安全永远比“方便”更重要。