Обнаружение кругового импорта - PullRequest
15 голосов
/ 09 марта 2010

Я работаю с проектом, который содержит около 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__)

Но это выглядит действительно противно, должен быть лучший способ сделать это, верно?

Ответы [ 4 ]

7 голосов
/ 09 марта 2010

Чтобы избежать необходимости изменять каждый модуль, вы можете вставить свою функцию отслеживания импорта в ловушку для импорта или в настраиваемую __import__, которую вы можете вставить во встроенные модули - последний на этот раз может работать лучше, потому что __import__ вызывается, даже если импортируемый модуль уже находится в sys.modules, что имеет место при циклическом импорте.

Для реализации я бы просто использовал набор модулей «в процессе импорта», что-то вроде (редактирование benjaoming: вставка рабочего фрагмента, полученного из оригинала):

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
1 голос
/ 09 марта 2010

Круговой импорт в Python не похож на PHP.

Импортированные модули Python загружаются в первый раз в «обработчик» импорта и хранятся там в течение всего процесса. Этот обработчик назначает имена в локальном пространстве имен для того, что импортируется из этого модуля, для каждого последующего импорта. Модуль является уникальным, и ссылка на это имя модуля всегда будет указывать на один и тот же загруженный модуль, независимо от того, куда он был импортирован.

Таким образом, если у вас есть циклический импорт модуля, загрузка каждого файла произойдет один раз, и тогда у каждого модуля будут имена, относящиеся к другому модулю, созданному в его пространстве имен.

Конечно, могут возникнуть проблемы при обращении к конкретным именам в обоих модулях (когда происходит циклический импорт ДО определения класса / функции, на которые ссылаются при импорте противоположных модулей), но вы получите ошибку, если это случается.

1 голос
/ 09 марта 2010

import использует __builtin__.__import__(), поэтому, если вы сделаете это, тогда каждый импорт повсеместно получит изменения. Обратите внимание, что циклический импорт - это , а не , но это обязательно проблема.

1 голос
/ 09 марта 2010

Не все циклические импорты являются проблемой, как вы обнаружили, когда исключение не выдается.

Если они представляют собой проблему, вы получите исключение при следующей попытке выполнить любой из ваших тестов. Когда это произойдет, вы можете изменить код.

Я не вижу каких-либо изменений в этой ситуации.

Пример, когда это не проблема:

a.py

import b
a = 42
def f():
  return b.b

b.py

import a
b = 42
def f():
  return a.a
...