代码之家  ›  专栏  ›  技术社区  ›  William Knight

为什么在使用execfile()运行的python脚本中不导入prevent nameerror?

  •  4
  • William Knight  · 技术社区  · 14 年前

    在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)为什么从引用“gamelib”的模块(script.py)或调用“gamelib”的模块(script_mgr.py)导入“gamelib”不起作用?

    这在Python2.5.1上发生

    2 回复  |  直到 14 年前
        1
  •  3
  •   Yukiko    14 年前

    Python documentation 对于execfile:

    execfile(文件名[,全局变量[,局部变量]])

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

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

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

    • 在游戏中 import gamelib 将gamelib模块导入 地球人和本地人 . 这是传递给script.py的环境,这就是为什么在scriptobject action方法中可以访问gamelib(从全局访问)。

    • 在脚本中 导入gamelib 将gamelib模块导入 当地人只 (不确定原因)。因此,当尝试从globals的scriptobject action方法访问gamelib时,会出现nameerror。如果您将导入移动到操作方法的作用域中(将从本地访问gamelib),它将工作:

      class ScriptObject:
          def action(self):
              import gamelib
              print("ScriptObject.action(): calling gamelib.play_sound()")
              gamelib.play_sound()
      
        2
  •  0
  •   William Knight    14 年前

    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的建议,将导入语句放在script对象成员函数的本地范围内。这对我来说似乎有点尴尬,也许有更好的方法来为exec脚本指定这样的导入,但至少我现在知道发生了什么。

    推荐文章