4

当脚本在 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():调用 gamelib.play_sound()
繁荣!
mhack:执行 $

但是,如果我注释掉 game.py 中的“import gamelib”语句并取消注释 script.py 中的“import gamelib”,我会收到以下错误:

mhack:exec $ python game.py
ScriptObject.action():调用 gamelib.play_sound()
回溯(最近一次通话最后):
  文件“game.py”,第 10 行,在
    主要的()
  文件“game.py”,第 8 行,在 main
    script_mgr.run()
  文件“/Users/williamknight/proj/test/python/exec/script_mgr.py”,第 12 行,运行中
    obj.action()
  文件“script.py”,第 9 行,正在运行
    gamelib.play_sound()
NameError:未定义全局名称“gamelib”

我的问题是:1)为什么在执行脚本的“game.py”模块中需要导入?2)为什么从引用它的模块(script.py)或调用它的模块(script_mgr.py)导入'gamelib'不起作用?

这发生在 Python 2.5.1

4

2 回答 2

3

来自 execfile 的Python 文档

execfile(文件名[,全局[,本地]])

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

execfile 有两个可选参数。由于您省略了它们,因此您的脚本正在调用 execfile 的环境中执行。因此,在 game.py 中的导入会改变行为。

另外,我在game.py和script.py中总结了如下import行为:

  • 在 game.pyimport gamelib中,将 gamelib 模块导入到globals 和 locals中。这是传递给 script.py 的环境,这就是为什么可以在 ScriptObject 操作方法(从全局访问)中访问 gamelib。

  • 在 script.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()
    
于 2010-02-27T19:42:57.237 回答
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)

这是程序的调试输出:

--- 游戏():BEF 执行:全局:
0000 __builtins__:
0001 __doc__:无
0002 __file__: 游戏.py
0003 __name__:__main__
0004 _game_global: BEF main()
0005 游戏库:
0006主:
0007 报告字典:
0008 脚本管理器:

--- 游戏():BEF 执行:本地人:

--- 游戏():AFT 执行:全局:
0000 __builtins__:
0001 __doc__:无
0002 __file__: 游戏.py
0003 __name__:__main__
0004 _game_global: 在 main(), AFT execfile()
0005 游戏库:
0006主:
0007 报告字典:
0008 脚本管理器:

--- 游戏():AFT 执行:本地人:
0000 脚本对象:__main__.ScriptObject
0001 游戏库:
0002 对象:
0003 数据库:
0004 脚本管理器:

--- ScriptObject.action(): 全局变量:
0000 __builtins__:
0001 __doc__:无
0002 __file__: 游戏.py
0003 __name__:__main__
0004 _game_global: 在 main(), AFT execfile()
0005 游戏库:
0006主:
0007 报告字典:
0008 脚本管理器:

--- ScriptObject.action(): 本地人:
0000 报告字典:
0001 自我:


繁荣!

我不会尝试将导入放在 game.py 或 script.py 的模块级别,而是按照 Yukiko 的建议将导入语句放在脚本对象成员函数的本地范围内。这对我来说似乎有点尴尬,并且可能有更好的方法来为 exec'd 脚本指定此类导入,但至少我现在明白发生了什么。

于 2010-02-28T16:21:22.063 回答