如前所述,动态代码生成是最难的代码生成方法。Python 中有一些工具可以让你生成
并执行代码,甚至可以对已编译的代码对象进行修改。关于这一点可以写一本完整的书,
即使这样也不能将这一话题完全写完。
许多项目(例如后面提到的Hy)都表明,利用代码生成技术,甚至整个语言都可以用
Python 重新实现。这说明其可能性几乎是无限的。知道了这个主题的范围之广以及它充满
各种易犯的错误,我甚至不会尝试给出关于如何用这种方法创建代码的详细建议,也不会
提供有用的代码示例。
无论如何,如果你打算独自深入研究这一领域,知道其用法对你可能是有用的。因此,
可以将本节仅作为对进一步学习的起点的简要总结。大多数内容都伴随有许多警告,以防
你在自己的项目中迫不及待地调用exec()和eval()。
1.exec、eval 和compile
Python 提供了3 个内置函数,用于手动执行、求值和编译任意Python 代码。
• exec(object, globals, locals):这一函数允许你动态执行Python 代码。
object 应该一个字符串或代码对象(参见compile()函数)。globals 和
locals 参数为所执行的代码提供全局的和局部的命名空间,这二者是可选的。如
果没有提供这两个参数,那么就在当前作用域中执行代码。如果提供了这两个参数,
globals 必须是字典,而locals 可以是任何映射对象。其返回值始终为None。
• eval(expression, globals, locals):这一函数用于对给定表达式进行求
值并返回其结果。它与exec()类似,但接受的expression 应该是单一Python
表达式,而不是一系列语句。它返回表达式求值的结果。
• compile(source, filename, mode):这一函数将源代码编译成代码对象或
AST 对象。要编译的代码在source 参数中作为字符串提供。filename 应该是
读取代码的文件。如果源文件是动态创建的,因此没有相关联的文件,那么一般用
作为它的值。mode 应该是exec(一系列语句)、eval(单一表达式)
或single(单一交互式语句,例如在Python 交互式会话中)。
如果你尝试动态生成代码,exec()和eval()函数是最容易上手的,因为它们可以对
字符串进行操作。如果你已经知道如何用Python 编程,那么你可能知道如何用编程的方式
正确地生成工作源代码。我希望你知道这一点。
对于元编程而言,最有用的显然是exec(),因为它可以执行任意Python 语句的序列。
看到“任意”两个字,你应该感到警觉。即使是eval(),只允许对高明的程序员负责的
表达式求值(用户自行输入),也会导致严重的安全漏洞。注意,使Python 解释器崩溃是
你应该担心的最不恐怖的情形。由于不负责任地使用exec()和eval(),从而引入远程
执行漏洞,这可能会有损你专业开发者的形象,甚至会让你失去工作。
即使输入的内容可信,但关于exec()和eval()仍有许多小细节,由于内容太多我
们这里不会列出,但它们会影响你的应用程序的工作方式,使其与你的预期不同。Armin
Ronacher 写过一篇很棒的文章,列出了其中最重要的细节,文章标题为:Be careful with exec
and eval in Python(参见http://lucumr.pocoo.org/2011/2/1/exec-in-python/)。
尽管有这些吓人的警告,但有些情况下使用exec()和eval()是非常合理的。关于
何时使用它们,流行的说法是:“到时你自然会知道。”换句话说,即使你有一丝的怀疑,也不应该使用它们,而应该尝试寻找其他解决方法。
eval()与不可信的输入
eval()函数的签名可能会让你觉得,如果你提供空的
globals 和locals 命名空间,并用合适的try …
except 语句来包装,那么它是相当安全的。大错特
错。Ned Batcheler 写过一篇很好的文章,展示了在不
访问内置函数的情况下,如何在eval()调用中引发
解释器分段错误(http://nedbatchelder.com/blog/201206/
eval_really_is_dangerous.html)。这证明了exec()和
eval()永远不应该与不可信的输入一同使用。