繁体   English   中英

为什么不导入会在运行execfile()的python脚本中阻止NameError?

[英]Why doesn't import prevent NameError in a python script run with execfile()?

当在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上

从execfile的Python文档

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

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

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

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

  • 在game.py中, import gamelib将gamelib模块导入全局变量和本地变量 这是传递给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() 

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM