Импорт модулей: __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..)