Python - 创建一个“脚本”系统
我正在制作一个wxpython应用程序,打算用各种工具把它打包成可以在多个平台上运行的可执行文件。
这个程序是一个用于基于瓷砖的游戏引擎的地图编辑器。
在这个应用中,我想提供一个脚本系统,让高级用户可以修改程序的行为,比如修改项目数据、把项目导出成不同的格式等等。
我希望这个系统的工作方式是这样的:
用户可以把他们想运行的Python脚本放到一个样式化的文本框里,然后按一个按钮来执行这个脚本。
到目前为止,我觉得这都很简单。获取文本框里的脚本作为字符串,用内置的compile()函数把它编译成一个代码对象,然后用exec语句来执行这个脚本。
script = textbox.text #bla bla store the string
code = compile(script, "script", "exec") #make the code object
eval(code, globals())
问题是,我想确保这个功能不会导致任何错误或bug。
比如说,如果脚本里有一个导入语句,这会不会造成问题,考虑到代码是用像py2exe或py2app这样的工具编译的?
我怎么确保用户不能破坏程序的关键部分,比如修改图形用户界面(GUI),同时又能让他们修改项目数据(这些数据保存在一个模块的全局属性中)?我觉得这可能意味着要修改传递给eval函数的globals字典。
我怎么确保这个eval不会因为长时间运行或无限循环而导致程序卡住?
我怎么确保用户代码中出现的错误不会让整个应用崩溃?
基本上,我想知道如何避免让用户运行自己代码时可能出现的所有这些问题。
编辑:关于给出的回答
我觉得到目前为止,没有任何回答真正解决我的问题。虽然部分问题得到了回答,但并不完全。我很清楚完全阻止不安全代码是不可能的。人们太聪明了,一个人(甚至一个团队)不可能想到所有绕过安全系统的方法。
实际上,我并不在乎他们是否能绕过。我更担心的是有人无意中破坏了他们不知道的东西。如果有人真的想,他们可以利用脚本功能把应用搞得一团糟,但我并不在乎。那是他们自己的实例,他们造成的问题在重启应用后就会消失,除非他们动了硬盘上的文件。我想防止用户做一些愚蠢的事情导致的问题。
比如IOError、SyntaxError、InfiniteLoopError等等。
关于作用域的部分已经得到了回答。我现在明白如何定义哪些函数和全局变量可以从eval
函数访问。
但有没有办法确保如果他们的代码执行时间过长,可以停止执行?
也许可以用一个绿色线程系统?(绿色是因为它会被eval处理,让用户不用担心线程安全)
另外,如果用户使用import module
语句从默认库中加载一个模块,而这个模块在类的其他部分没有被使用,这会不会导致应用在Py2exe、Py2app或Freeze下被冻结?如果他们调用了一个不在标准库中的模态,这样做是否足够,只要这个模态和打包的可执行文件在同一个目录下?
我希望能在不创建新问题的情况下得到这些答案,但如果必须的话,我会这样做。
4 个回答
如果你可以选择使用Tkinter,那么你可以利用它自带的tcl解释器来处理你的脚本。其实,如果你不启动tk的事件循环,使用wxpython应用程序也可以做到这一点;只需使用tcl解释器,而不创建任何窗口就行。
因为tcl解释器是一个独立的东西,所以只要你小心选择给tcl的命令,几乎不可能会让python解释器崩溃。而且,tcl让创建领域特定语言(DSL)变得非常简单。
Python - 唯一一个内置脚本引擎的脚本语言 :-).
简短回答:不可以
其他相关帖子:
建立一个安全的系统并不简单。细节太多,而且有很多聪明的黑客手段:
关于你的设计目标:
看起来你想要建立一个可扩展的系统,让用户能够修改很多行为和逻辑。
最简单的办法是让他们写一个脚本,你可以在程序运行时执行这个脚本(eval)。
但是,一个好的设计应该清楚地描述灵活性范围,并通过各种设计方案提供脚本机制,比如配置、插件和脚本能力等。如果脚本的接口定义得好,可以提供更有意义的扩展性,同时也更安全。
简单的说:别这样做。
你可以禁止某些关键词(比如 import
)和操作,以及限制对某些数据结构的访问,但最终你还是在给那些高级用户很大的权限。因为这是一个在用户机器上运行的客户端,如果有恶意用户,他们可以让整个应用崩溃,甚至搞坏它。如果他们真的想这么做,那就是他们自己的实例崩溃。你需要把这些情况写清楚,并告诉大家哪些地方不要碰。
话虽如此,我之前也做过类似的事情,针对那些执行用户输入的网页应用,确实也用过像 eval
这样的调用:
eval(code, {"__builtins__":None}, {safe_functions})
这里的 safe_functions
是一个字典,里面存放着 {"name": func}
这种格式的函数对,表示你希望用户能够访问的函数。如果有一些你非常确定用户绝对不会想碰的数据结构,记得在传递给他们之前把它从 globals
中移除。
顺便提一下,Guido 在他博客上也讨论过这个问题。我会看看能不能找到那篇文章。
编辑: 找到了。