Почему не предотвращается импорт 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 (): вызов gamelib.play_sound() бум! mhack:exec $
Однако, если я закомментирую оператор import gamelib в game.py и раскомментирую import gamelib в script.py, я получу следующую ошибку:
mhack: exec $ python game.py ScriptObject.action (): вызов gamelib.play_sound() Traceback (последний вызов был последним): Файл "game.py", строка 10, в главный() Файл "game.py", строка 8, в основном 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) Почему не получается импортировать 'gamelib' из модуля, на который есть ссылка (script.py), или из модуля, где он вызывается (script_mgr.py)?
Это происходит на Python 2.5.1
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()
Причина, по которой "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__: нет 0002 __file__: game.py 0003 __name__: __main__ 0004 _game_global: BEF main() 0005 gamelib: 0006 основной: 0007 report_dict: 0008 script_mgr: --- game (): BEF exec: localals: --- game (): AFT exec: globals: 0000 __builtins__: 0001 __doc__: нет 0002 __file__: game.py 0003 __name__: __main__ 0004 _game_global: в main(), AFT execfile() 0005 gamelib: 0006 основной: 0007 report_dict: 0008 script_mgr: --- игра (): исполнительный директор AFT: местные жители: 0000 ScriptObject: __main__.ScriptObject 0001 gamelib: 0002 объект: 0003 pdb: 0004 script_mgr: --- ScriptObject.action(): globals: 0000 __builtins__: 0001 __doc__: нет 0002 __file__: game.py 0003 __name__: __main__ 0004 _game_global: в main(), AFT execfile() 0005 gamelib: 0006 основной: 0007 report_dict: 0008 script_mgr: --- ScriptObject.action(): locals: 0000 report_dict: 0001 себя: бум!
Вместо того, чтобы пытаться поместить import в game.py или на уровне модуля script.py, я последую предложению Юкико поместить операторы import в локальную область функций-членов объекта script. Это кажется мне немного неловким, и может быть какой-то лучший способ указать такой импорт для сценариев exec'd, но, по крайней мере, теперь я понимаю, что происходит.