Почему импорт не предотвращает NameError в скрипте python, запускаемом с помощью execfile()?

Я просмотрел ряд существующих вопросов об исключениях NameError, когда скрипты запускаются с операторами exec или execfile() в Python, но пока не нашел хорошего объяснения следующего поведения.

Я хочу сделать простую игру, которая создает объекты сценария во время выполнения с помощью 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 $ 

Однако, если я закомментирую оператор «import gamelib» в game.py и раскомментирую «import gamelib» в script.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) Зачем нужен импорт в модуле 'game.py', который выполняет скрипт? 2) Почему не работает импорт 'gamelib' из модуля, в котором он указан (script.py), или из модуля, в котором он вызывается (script_mgr.py)?

Это происходит на Python 2.5.1


person William Knight    schedule 27.02.2010    source источник


Ответы (2)


Из документации Python для execfile:

execfile(имя файла[, глобальные [, локальные]])

Если словарь locals опущен, по умолчанию используется словарь globals. Если оба словаря опущены, выражение выполняется в среде, где вызывается execfile().

Есть два необязательных аргумента для execfile. Поскольку вы опускаете их оба, ваш сценарий выполняется в среде, где вызывается execfile. Отсюда причина, по которой импорт в game.py меняет поведение.

Кроме того, я сделал вывод о следующем поведении импорта в game.py и script.py:

  • В game.py import gamelib импортирует модуль gamelib в глобальные и локальные файлы. Это среда, переданная в script.py, поэтому gamelib доступна в методе действия ScriptObject (доступ из глобальных переменных).

  • В script.py import gamelib импортирует модуль gamelib в только локальные файлы (не уверен в причине). Поэтому при попытке доступа к gamelib из метода действия ScriptObject из глобальных переменных возникает ошибка NameError. Это будет работать, если вы переместите импорт в область действия метода следующим образом (gamelib будет доступен из локальных файлов):

    class ScriptObject:
        def action(self):
            import gamelib
            print("ScriptObject.action(): calling gamelib.play_sound()")
            gamelib.play_sound()
    
person Yukiko    schedule 27.02.2010
comment
Я знал об аргументах глобальных и локальных переменных, но до сих пор не уверен, как их лучше всего использовать. Ваша цитата об окружающей среде помогает мне понять немного лучше, но я все еще не понимаю, почему импорт в script.py не работает - разве это не поместит его в среду? - person William Knight; 28.02.2010
comment
Обновил свой ответ после некоторого тестирования, распечатав глобальные и локальные значения. Надеюсь, поможет ;) - person Yukiko; 28.02.2010
comment
Да, помогает! После ваших наблюдений за влиянием импорта на глобальные и локальные переменные я добавил некоторый код отладки для вывода глобальных и локальных переменных, и теперь это действительно проясняет ситуацию. Я приму ваш ответ, но также опубликую дополнительный ответ с моими результатами. - person William Knight; 28.02.2010

Причина, по которой 'import gamelib' в script.py не работает, заключается в том, что он импортирует в локальную область видимости 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, я последую предложению Юкико поместить операторы импорта в локальную область функций-членов объекта скрипта. Мне это кажется немного неудобным, и может быть какой-то лучший способ указать такой импорт для исполняемых сценариев, но, по крайней мере, теперь я понимаю, что происходит.

person William Knight    schedule 28.02.2010
comment
Теперь я нашел лучший способ, чем выполнять импорт в локальной области функций-членов, я просто указываю env, выполнив: execfile(script.py, script_mgr.env()) где script_mgr.env() просто возвращает глобальные значения( ) dict для модуля script_mgr. Это обеспечивает среду для импорта script.py, которая остается в области действия. - person William Knight; 28.02.2010