为什么 import 不能防止使用 execfile() 运行的 python 脚本中出现 NameError?
题
我查看了许多有关在 Python 中使用 exec 语句或 execfile() 运行脚本时出现 NameError 异常的现有问题,但尚未找到对以下行为的良好解释。
我想制作一个简单的游戏,在运行时使用 execfile() 创建脚本对象。下面是演示该问题的 4 个模块(请耐心等待,这是我能做到的最简单的!)。主程序只是使用 execfile() 加载脚本,然后调用脚本管理器来运行脚本对象:
# game.py
import script_mgr
import gamelib # must be imported here to prevent NameError, any place else has no effect
def main():
execfile("script.py")
script_mgr.run()
main()
脚本文件只是创建一个播放声音的对象,然后将该对象添加到脚本管理器的列表中:
script.py
import script_mgr
#import gamelib # (has no effect here)
class ScriptObject:
def action(self):
print("ScriptObject.action(): calling gamelib.play_sound()")
gamelib.play_sound()
obj = ScriptObject()
script_mgr.add_script_object(obj)
脚本管理器只调用每个脚本的action()函数:
# script_mgr.py
#import gamelib # (has no effect here)
script_objects = []
def add_script_object(obj):
script_objects.append(obj)
def run():
for obj in script_objects:
obj.action()
gamelib 函数定义在第四个模块中,这是访问起来比较麻烦的一个:
# gamelib.py
def play_sound():
print("boom!")
上述代码适用于以下输出:
mhack:exec $ python game.py ScriptObject.action(): calling gamelib.play_sound() boom! mhack:exec $
但是,如果我注释掉 game.py 中的“import gamelib”语句并取消注释 script.py 中的“import gamelib”,则会出现以下错误:
mhack:exec $ python game.py ScriptObject.action(): calling gamelib.play_sound() Traceback (most recent call last): File "game.py", line 10, in main() File "game.py", line 8, in main script_mgr.run() File "/Users/williamknight/proj/test/python/exec/script_mgr.py", line 12, in run obj.action() File "script.py", line 9, in action gamelib.play_sound() NameError: global name 'gamelib' is not defined
我的问题是:1)为什么需要在执行脚本的“game.py”模块中导入?2)为什么从引用它的模块(script.py)或调用它的模块(script_mgr.py)导入'gamelib'不起作用?
这发生在 Python 2.5.1 上
解决方案
来自 Python 文档 对于执行文件:
execfile(文件名[,全局变量[,局部变量]])
如果省略本地字典,则默认为全局字典。如果两个字典都被省略,则表达式将在调用 execfile() 的环境中执行。
execfile 有两个可选参数。由于您忽略了它们,因此您的脚本将在调用 execfile 的环境中执行。因此,game.py 中的导入会改变行为。
另外,我总结了game.py和script.py中导入的以下行为:
在游戏.py中
import gamelib
将 gamelib 模块导入 全局变量和局部变量. 。这是传递给 script.py 的环境,这就是为什么可以在 ScriptObject 操作方法中访问 gamelib(从全局变量访问)。在脚本.py中
import gamelib
将 gamelib 模块导入 仅限当地人 (不确定原因)。因此,当尝试从全局变量的 ScriptObject 操作方法访问 gamelib 时,您会遇到 NameError。如果您将导入移动到操作方法的范围内,它将起作用(将从本地访问 gamelib):class ScriptObject: def action(self): import gamelib print("ScriptObject.action(): calling gamelib.play_sound()") gamelib.play_sound()
其他提示
在script.py“导入gamelib”没有效果的原因是因为它进口game.py main()中的局部范围的,因为这是在其中执行导入的范围。这个范围是不是ScriptObject.action()在执行时可见范围。
添加调试代码到打印出全局()的变化和当地人()揭示了什么是在程序的以下修改版本要去:
# game.py
import script_mgr
import gamelib # puts gamelib into globals() of game.py
# a debug global variable
_game_global = "BEF main()"
def report_dict(d):
s = ""
keys = d.keys()
keys.sort()
for i, k in enumerate(keys):
ln = "%04d %s: %s\n" % (i, k, d[k])
s += ln
return s
def main():
print("--- game(): BEF exec: globals:\n%s" % (report_dict(globals())))
print("--- game(): BEF exec: locals:\n%s" % (report_dict(locals())))
global _game_global
_game_global = "in main(), BEF execfile()"
execfile("script.py")
_game_global = "in main(), AFT execfile()"
print("--- game(): AFT exec: globals:\n%s" % (report_dict(globals())))
print("--- game(): AFT exec: locals:\n%s" % (report_dict(locals())))
script_mgr.run()
main()
# script.py
import script_mgr
import gamelib # puts gamelib into the local scope of game.py main()
import pdb # a test import that only shows up in the local scope of game.py main(). It will _not_ show up in any visible scope of ScriptObject.action()!
class ScriptObject:
def action(self):
def report_dict(d):
s = ""
keys = d.keys()
keys.sort()
for i, k in enumerate(keys):
ln = "%04d %s: %s\n" % (i, k, d[k])
s += ln
return s
print("--- ScriptObject.action(): globals:\n%s" % (report_dict(globals())))
print("--- ScriptObject.action(): locals:\n%s" % (report_dict(locals())))
gamelib.play_sound()
obj = ScriptObject()
script_mgr.add_script_object(obj)
下面是该程序的调试输出:
--- game(): BEF exec: globals: 0000 __builtins__: 0001 __doc__: None 0002 __file__: game.py 0003 __name__: __main__ 0004 _game_global: BEF main() 0005 gamelib: 0006 main: 0007 report_dict: 0008 script_mgr: --- game(): BEF exec: locals: --- game(): AFT exec: globals: 0000 __builtins__: 0001 __doc__: None 0002 __file__: game.py 0003 __name__: __main__ 0004 _game_global: in main(), AFT execfile() 0005 gamelib: 0006 main: 0007 report_dict: 0008 script_mgr: --- game(): AFT exec: locals: 0000 ScriptObject: __main__.ScriptObject 0001 gamelib: 0002 obj: 0003 pdb: 0004 script_mgr: --- ScriptObject.action(): globals: 0000 __builtins__: 0001 __doc__: None 0002 __file__: game.py 0003 __name__: __main__ 0004 _game_global: in main(), AFT execfile() 0005 gamelib: 0006 main: 0007 report_dict: 0008 script_mgr: --- ScriptObject.action(): locals: 0000 report_dict: 0001 self: boom!
而不是试图把在game.py或script.py的模块级的进口,我会按照由纪子的建议,把import语句的脚本对象的成员函数的局部范围。这似乎有点尴尬的我,有可能会指定这样的进口exec'd脚本时的一些更好的办法,但至少现在我明白发生了什么。