Импорт модулей: __main__ vs import в качестве модуля
В предисловии, я думаю, я, возможно, понял, как заставить этот код работать (на основе Изменение переменных модуля после импорта), но мой вопрос действительно о том, почему происходит следующее поведение, поэтому я могу понять, чего не делать в будущем.
У меня есть три файла. Первый - mod1.py:
# mod1.py
import mod2
var1A = None
def func1A():
global var1
var1 = 'A'
mod2.func2()
def func1B():
global var1
print var1
if __name__ == '__main__':
func1A()
Далее у меня есть mod2.py:
# mod2.py
import mod1
def func2():
mod1.func1B()
Наконец, у меня есть driver.py:
# driver.py
import mod1
if __name__ == '__main__':
mod1.func1A()
Если я выполню команду python mod1.py
, то выход будет None
. Основываясь на ссылке, на которую я ссылался выше, кажется, что существует некоторая разница между mod1.py
, импортируемой как __main__
и mod1.py
, которая импортируется из mod2.py
. Поэтому я создал driver.py
. Если я выполню команду python driver.py
, тогда я получу ожидаемый результат: A
. Я как бы вижу разницу, но я действительно не вижу механизм или причину этого. Как и почему это происходит? Кажется противоречивым, что тот же модуль будет существовать дважды. Если я выполняю python mod1.py
, можно ли получить доступ к переменным в __main__
версии mod1.py
вместо переменных в версии, импортированной с помощью mod2.py
?
Ответы
Ответ 1
В переменной __name__
всегда указано имя модуля, кроме случаев, когда файл был загружен в интерпретатор как script. Затем вместо этой переменной устанавливается строка '__main__'
.
В конце концов, script запускается как основной файл всей программы, все остальные модули напрямую или косвенно импортируются этим основным файлом. При тестировании переменной __name__
вы можете обнаружить, был ли файл импортирован как модуль или был запущен напрямую.
Внутри модуля предоставляется словарь пространства имен, который хранится как часть метаданных для каждого модуля, в sys.modules
. Основной файл, выполненный script, хранится в той же структуре, что и '__main__'
.
Но когда вы импортируете файл в качестве модуля, python сначала смотрит в sys.modules
, чтобы увидеть, был ли этот модуль уже импортирован раньше. Итак, import mod1
означает, что мы сначала смотрим в sys.modules
для модуля mod1
. Он создаст новую структуру модуля с пространством имен, если mod1
еще не существует.
Итак, если вы оба запускаете mod1.py
в качестве основного файла, а затем импортируете его как модуль python, он получит две записи пространства имен в sys.modules
. Один как '__main__'
, а затем как 'mod1'
. Эти два пространства имен полностью разделены. Ваш глобальный var1
хранится в sys.modules['__main__']
, но func1B
ищет sys.modules['mod1']
для var1
, где он None
.
Но когда вы используете python driver.py
, driver.py
становится основным файлом '__main__'
программы, а mod1
будет импортироваться только один раз в структуру sys.modules['mod1']
. На этот раз func1A
хранит var1
в структуре sys.modules['mod1']
и что найдет func1B
.
Ответ 2
Что касается практического решения для использования модуля в качестве основного script - поддержки согласованного кросс-импорта:
Решение 1:
См. в модуле pdb Python, как он запускается как script путем импорта самого себя при выполнении в качестве __main__
(в конце):
#! /usr/bin/env python
"""A Python debugger."""
# (See pdb.doc for documentation.)
import sys
import linecache
...
# When invoked as main program, invoke the debugger on a script
if __name__ == '__main__':
import pdb
pdb.main()
Просто я бы рекомендовал реорганизовать запуск __main__
в начало script следующим образом:
#! /usr/bin/env python
"""A Python debugger."""
# When invoked as main program, invoke the debugger on a script
import sys
if __name__ == '__main__':
##assert os.path.splitext(os.path.basename(__file__))[0] == 'pdb'
import pdb
pdb.main()
sys.exit(0)
import linecache
...
Таким образом, тело модуля не выполняется дважды, что является "дорогостоящим", нежелательным и иногда критическим.
Решение 2:
В более редких случаях желательно, чтобы фактический script модуль __main__
отображался прямо как фактический псевдоним модуля (mod1
):
# mod1.py
import mod2
...
if __name__ == '__main__':
# use main script directly as cross-importable module
_mod = sys.modules['mod1'] = sys.modules[__name__]
##_modname = os.path.splitext(os.path.basename(os.path.realpath(__file__)))[0]
##_mod = sys.modules[_modname] = sys.modules[__name__]
func1A()
Известные недостатки:
-
reload(_mod)
не работает
- для макетированных классов потребуются дополнительные сопоставления для рассыпания (
find_global
..)