运行包含在字符串中的Python代码
我正在用pygame和box2d写一个游戏引擎,在角色构建器里,我想能写一些代码,这些代码会在按下键的时候执行。
我的计划是在角色构建器里放一个文本编辑器,让你可以写类似这样的代码:
if key == K_a:
## Move left
pass
elif key == K_d:
## Move right
pass
我会把文本编辑器里的内容提取出来,作为一个字符串,然后我想在这个角色的方法里运行这些代码:
def keydown(self, key):
## Run code from text editor
这样做的最佳方法是什么呢?
4 个回答
你可以使用 eval()
这个函数。
正如其他人提到的,你可以把文本加载到一个字符串里,然后使用 exec "codestring"
来执行它。如果这个代码已经在一个文件里,使用 execfile 就可以避免先加载它。
有一点性能上的建议:你应该避免多次执行同样的代码,因为解析和编译 Python 源代码是一个比较慢的过程。也就是说,不要这样做:
def keydown(self, key):
exec user_code
你可以通过把源代码编译成一个代码对象(使用 compile()
),然后执行这个对象来稍微提高性能,或者更好的是,构建一个函数并保留它,只需构建一次。这样用户需要写 "def my_handler(args...)",或者你自己在前面加上,然后做类似这样的事情:
user_source = "def user_func(args):\n" + '\n'.join(" "+line for line in user_source.splitlines())
d={}
exec user_source in d
user_func = d['user_func']
然后在后面:
if key == K_a:
user_func(args)
你可以使用 eval(string)
方法来实现这个功能。
定义
eval(code, globals=None, locals=None)
这里的代码就是标准的 Python 代码,这意味着它需要正确缩进。
globals 可以定义一个自定义的 __builtins__
,这在安全方面可能会很有用。
示例
eval("print('Hello')")
这段代码会在控制台打印 hello
。你还可以为代码指定局部和全局变量:
eval("print('Hello, %s'%name)", {}, {'name':'person-b'})
安全问题
不过要小心,任何用户输入都会被执行。想想看:
eval("import os;os.system('sudo rm -rf /')")
有很多方法可以解决这个问题。最简单的做法是:
eval("import os;...", {'os':None})
这样会抛出一个异常,而不是擦除你的硬盘。虽然你的程序是在桌面上运行,但如果有人重新分发脚本,这可能会成为一个问题,我想这也是你不希望的。
奇怪的示例
这里有一个使用 eval
的奇怪示例:
def hello() : print('Hello')
def world() : print('world')
CURRENT_MOOD = 'happy'
eval(get_code(), {'contrivedExample':__main__}, {'hi':hello}.update(locals()))
在 eval 行中,这段代码做了以下几件事:
- 给当前模块起了个新名字(在脚本中变成了
contrivedExample
)。现在使用者可以调用contrivedExample.hello()
。) - 将
hi
定义为指向hello
- 将这个字典与当前模块中的全局变量列表结合起来。
失败
结果是(感谢评论者!)你实际上需要使用 exec
语句。真是个大失误。修正后的示例如下:
exec
定义
(这听起来很熟悉!)
Exec 是一个语句:
exec "code" [in scope]
其中 scope 是一个包含局部和全局变量的字典。如果没有指定,它将在当前作用域中执行。
这里的代码也是标准的 Python 代码,这意味着它仍然需要正确缩进。
exec
示例
exec "print('hello')"
这段代码会在控制台打印 hello
。你也可以为代码指定局部和全局变量:
eval "print('hello, '+name)" in {'name':'person-b'}
exec
安全问题
不过要小心,任何用户输入都会被执行。想想看:
exec "import os;os.system('sudo rm -rf /')"
打印语句
正如评论者所提到的,print
在 Python 3.0 之前的所有版本中都是一个语句。在 2.6 中,可以通过输入 from __future__ import print_statement
来改变其行为。否则,请使用:
print "hello"
而不是:
print("hello")