Почему импорт не предотвращает 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
Решение
Из Документация по 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 action из globals вы получаете NameError .Это сработает, если вы переместите импорт в область действия метода action следующим образом (доступ к gamelib будет осуществляться из локальных файлов).:class ScriptObject: def action(self): import gamelib print("ScriptObject.action(): calling gamelib.play_sound()") gamelib.play_sound()
Другие советы
Причина, по которой '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, я последую предложению Юкико поместить инструкции import в локальную область видимости функций-членов объекта script.Мне это кажется немного неудобным, и, возможно, есть какой-то лучший способ указать такой импорт для исполняемых сценариев, но, по крайней мере, я теперь понимаю, что происходит.