Я работаю с проектом, который содержит приблизительно 30 уникальных модулей. Это не было разработано слишком хорошо, таким образом, распространено, что я создаю круговой импорт при добавлении некоторой новой функциональности к проекту.
Конечно, когда я добавляю круговой импорт, я не знаю о нем. Иногда довольно очевидно, что я сделал круговой импорт, когда я получаю ошибку как AttributeError: 'module' object has no attribute 'attribute'
где я ясно определил 'attribute'
. Но другие времена, код не выдает исключения из-за способа, которым он используется.
Так, к моему вопросу:
Действительно ли возможно программно обнаружить, когда и где круговой импорт происходит?
Единственное решение, о котором я могу думать до сих пор, состоит в том, чтобы иметь модуль importTracking
это содержит dict importingModules
, функция importInProgress(file)
, который увеличивает importingModules[file]
, и бросает ошибку, если это больше, чем 1, и функция importComplete(file)
который постепенно уменьшается importingModules[file]
. Все другие модули были бы похожи:
import importTracking
importTracking.importInProgress(__file__)
#module code goes here.
importTracking.importComplete(__file__)
Но это выглядит действительно противным, там получен, чтобы быть лучшим способом сделать это, правильно?
Чтобы избежать изменения каждого модуля, вы можете закрепить свои функции отслеживания импорта в ловушке импорта или в настраиваемом __ import __
, который можно вставить во встроенные модули - - последнее, на этот раз, может работать лучше, потому что __ import __
вызывается, даже если импортируемый модуль уже находится в sys.modules
, что имеет место при циклическом импорте.
Для реализации я бы просто использовал набор модулей «в процессе импорта», что-то вроде (benjaoming edit: вставка рабочего фрагмента, полученного из оригинала):
beingimported = set()
originalimport = __import__
def newimport(modulename, *args, **kwargs):
if modulename in beingimported:
print "Importing in circles", modulename, args, kwargs
print " Import stack trace -> ", beingimported
# sys.exit(1) # Normally exiting is a bad idea.
beingimported.add(modulename)
result = originalimport(modulename, *args, **kwargs)
if modulename in beingimported:
beingimported.remove(modulename)
return result
import __builtin__
__builtin__.__import__ = newimport
Циклический импорт в Python не похож на PHP.
Импортированные модули Python загружаются в первый раз в «обработчик» импорта и хранятся там на протяжении всего процесса. Этот обработчик назначает имена в локальном пространстве имен всему, что импортируется из этого модуля, для каждого последующего импорта.Модуль уникален, и ссылка на это имя модуля всегда будет указывать на один и тот же загруженный модуль, независимо от того, куда он был импортирован.
Итак, если у вас есть циклический импорт модуля, загрузка каждого файла будет происходить один раз, а затем каждый модуль будет иметь имена, относящиеся к другому модулю, созданному в его пространстве имен.
Конечно, могут возникнуть проблемы при обращении к конкретным именам в обоих модулях (когда циклический импорт происходит ДО определений классов / функций, на которые есть ссылки в импорте противоположных модулей), но вы получите сообщение об ошибке, если это бывает.
Не весь циклический импорт является проблемой, как вы обнаружили, когда исключение не генерируется.
Когда они представляют собой проблему, вы получите исключение при следующей попытке запустить любой из ваших тестов. В этом случае вы можете изменить код.
Я не вижу необходимости в каких-либо изменениях в этой ситуации.
Пример, когда это не проблема:
import b
a = 42
def f():
return b.b
import a
b = 42
def f():
return a.a
import
использует __ builtin __.__ import __ ()
, поэтому, если вы установите его monkeypatch, тогда каждый импорт повсюду будет принимать изменения. Обратите внимание, что циклический импорт не обязательно является проблемой .