为什么在使用execfile()运行的python脚本中,import不能阻止NameError?

4 投票
2 回答
1193 浏览
提问于 2025-04-15 19:48

我看了很多关于在Python中使用exec语句或execfile()时出现NameError异常的问题,但还没有找到对以下行为的好解释。

我想做一个简单的游戏,运行时通过execfile()创建脚本对象。下面有四个模块展示了这个问题(请耐心点,这是我能做到的最简单的了!)。主程序只是用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'模块中需要import?2)为什么从引用它的模块(script.py)或调用它的模块(script_mgr.py)中导入'gamelib'不行?

这个问题发生在Python 2.5.1上。

2 个回答

0

在script.py中使用'import gamelib'没有效果,是因为它只在game.py的main()函数的局部范围内有效。也就是说,这个导入操作只在执行的这个范围内有效,而当ScriptObject.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()
# 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的模块级别,我决定按照Yukiko的建议,把导入语句放在脚本对象成员函数的局部范围内。虽然这样做让我觉得有点别扭,但至少我现在明白发生了什么。

3

这是关于execfile的内容,来自Python文档

execfile(filename[, globals[, locals]])

如果不提供locals字典,它会默认使用globals字典。如果两个字典都没有提供,表达式会在调用execfile()的环境中执行。

execfile有两个可选的参数。因为你都没有提供,所以你的脚本是在调用execfile的环境中执行的。这就是为什么在game.py中的导入会改变行为的原因。

此外,我总结了game.py和script.py中导入的以下行为:

  • 在game.py中,import gamelib会把gamelib模块导入到全局和局部环境中。这就是为什么在ScriptObject的动作方法中可以访问gamelib(因为它在全局中可用)。

  • 在script.py中,import gamelib只会把gamelib模块导入到局部环境中(不太确定原因)。所以当你尝试从全局环境访问gamelib时,会出现NameError错误。如果你把导入放到动作方法的范围内,就可以正常工作(gamelib会从局部环境中访问):

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

撰写回答