为什么import不防止在使用execfile()运行的python脚本中出现NameError?

2024-09-28 21:08:10 发布

您现在位置:Python中文网/ 问答频道 /正文

在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()

脚本文件只创建一个播放声音的对象,然后将该对象添加到脚本管理器中的列表中:

^{pr2}$

脚本管理器只调用每个脚本的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 $ 

但是,如果我在游戏.py并取消注释中的“import gamelib”脚本.py,我得到以下错误:

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)为什么在游戏.py'模块,执行脚本的模块?2) 为什么不能从引用它的模块导入“gamelib”(脚本.py)或者调用它的模块(脚本_py经理)? 在

这在python2.5.1上发生


Tags: 模块对象inpyimport脚本gameobj
2条回答

从execfile的Python documentation中:

执行文件(filename[,globals[,locals]])

如果省略locals字典,则默认为globals字典。如果两个字典都被省略,则表达式将在调用execfile()的环境中执行。

execfile有两个可选参数。由于忽略了这两个参数,所以脚本将在调用execfile的环境中执行。因此进口的原因游戏.py改变行为。在

此外,我还总结了以下进口行为游戏.py以及脚本.py公司名称:

  • 在游戏.pyimport gamelib将gamelib模块导入到全局和局部。这是传递给脚本.py这就是为什么gamelib可以在ScriptObject操作方法(从全局访问)中访问。

  • 在脚本.pyimport gamelib只将gamelib模块导入到局部变量中(不确定原因)。因此,当尝试从全局变量的ScriptObject操作方法访问gamelib时,会出现NameError。如果您将导入移动到action方法的作用域中(将从本地访问gamelib),它将起作用:

    class ScriptObject:
        def action(self):
            import gamelib
            print("ScriptObject.action(): calling gamelib.play_sound()")
            gamelib.play_sound()
    

“导入游戏库”的原因脚本.py没有效果是因为它进口到了本地范围内游戏.pymain(),因为这是执行导入的范围。此作用域不是的可见作用域脚本对象.action()执行时。在

添加调试代码以打印globals()和locals()中的更改,将显示以下修改后的程序版本中发生的情况:

# 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()
^{pr2}$

以下是程序的调试输出:

 - 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!

而不是试图进口游戏.py或模块级别脚本.py,我将遵循Yukiko的建议,将import语句放在脚本对象成员函数的本地范围内。这在我看来有点尴尬,也许有更好的方法可以为exec脚本指定这样的导入,但至少我现在明白了发生了什么。在

相关问题 更多 >