Инструмент для точного определения кругового импорта в Python / Django? - PullRequest
30 голосов
/ 01 февраля 2012

У меня есть приложение Django, и где-то в нем есть рекурсивный импорт, который вызывает проблемы.Из-за размера приложения у меня возникла проблема с точным указанием причины циклического импорта.

Я знаю, что ответ «просто не пишите циклический импорт», но проблема в том, что у меня возникла проблематрудно понять, откуда происходит циклический импорт, поэтому в идеале идеальным будет инструмент, который проследил импорт до его источника.

Существует ли такой инструмент?За исключением этого, я чувствую, что делаю все от меня зависящее, чтобы избежать проблем кругового импорта - перемещая импорт по возможности вниз страницы, перемещая их внутри функций, а не располагая их вверху и т. Д., Но по-прежнему сталкиваясь с проблемами,Мне интересно, есть ли какие-нибудь советы или хитрости, чтобы вообще их избегать.

Чтобы уточнить ...

В Django, особенно когда он встречает циклический импорт, иногда выдает ошибкуно иногда он проходит молча , но приводит к ситуации, когда определенных моделей или полей просто нет .К сожалению, это часто происходит в одном контексте (скажем, на сервере WSGI), а не в другом (оболочка).Так что тестирование в оболочке примерно так будет работать:

Foo.objects.filter(bar__name='Test')

, но в сети выдает ошибку:

FieldError: Невозможно разрешить ключевое слово 'bar__name' в поле.Возможны следующие варианты: ...

С явно пропущенными несколькими полями.

Так что не может быть простой проблемы с кодом, поскольку он работает воболочка, но не через веб-сайт.

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

Ответы [ 4 ]

33 голосов
/ 10 февраля 2012

Причина ошибки импорта легко найти в обратном следе исключения ImportError.

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

Распространенные причины в Django:

  1. Импорт подпакета из совершенно другогоmodule,

    например, from mymodule.admin.utils import ...

    Сначала будет загружен admin/__init__.py, что, вероятно, импортирует во время загрузки другие пакеты (например, модели, представления администратора).Представление администратора инициализируется с помощью admin.site.register(..), чтобы конструктор мог начать импортировать больше материала.В какой-то момент это может привести к тому, что ваш модуль выдаст первое утверждение.

    У меня было такое утверждение в моем промежуточном программном обеспечении, вы можете догадаться, где это закончилось.;)

  2. Смешивание полей формы, виджетов и моделей.

    Поскольку модель может предоставлять «поле формы», вы начинаете импортировать формы.У него есть виджет.Этот виджет имеет несколько констант из ... э ... модели.И теперь у вас есть петля.Лучше импортировать этот класс поля формы внутри тела def formfield() вместо глобальной области видимости модуля.

  3. A managers.py, который ссылается на константы models.py

    ПослеВ общем, модель в первую очередь нуждается в менеджере.Менеджер не может начать импорт models.py, поскольку он все еще инициализируется.См. Ниже, потому что это самая простая ситуация.

  4. Использование ugettext() вместо ugettext_lazy.

    Когда вы используете ugettext(), система перевода нуждаетсяинициализировать.Он запускает сканирование всех пакетов в INSTALLED_APPS и ищет пакет locale.XY.formats.Когда ваше приложение только инициализировалось, оно теперь снова импортируется при глобальном сканировании модуля.

    Аналогичные вещи происходят со сканированием плагинов, поиском по индексу haystack и другими подобными механизмами.

  5. Слишком много в __init__.py.

    Это комбинация пунктов 1 и 4, она подчеркивает систему импорта, потому что импорт подпакета сначала инициализирует все родительские пакеты.По сути, для простого импорта выполняется много кода, что увеличивает необходимость импорта чего-либо из неправильного места.

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

# models.py:
from django.db import models
from mycms.managers import PageManager

class Page(models.Model)
    PUBLISHED = 1

    objects = PageManager()

    # ....


# managers.py:
from django.db import models

class PageManager(models.Manager):
    def published(self):
        from mycms.models import Page   # Import here to prevent circular imports
        return self.filter(status=Page.PUBLISHED)

В этом случае вы видите, что models.py действительно нужно импортировать managers.py;без этого он не может выполнить статическую инициализацию PageManager.Наоборот, не так критично.Модель Page может быть легко импортирована внутри функции, а не глобально.

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

11 голосов
/ 10 февраля 2012

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

class MyModel(models.Model):
    myfk = models.ForeignKey(
        'myapp.MyAppModel',  # avoid circular imports
        null=True)

См .: https://docs.djangoproject.com/en/dev/ref/models/fields/#foreignkey

7 голосов
/ 09 февраля 2012

Что я обычно делаю, когда сталкиваюсь с ошибкой импорта, работаю в обратном направлении. Я получил бы сообщение об ошибке «невозможно импортировать xyz from myproject.views», хотя xyz существует просто отлично. Затем я делаю две вещи:

  • Я создаю свой собственный код для каждого импорта myproject.views и создаю (мысленный) список модулей, которые его импортируют.

  • Я проверяю, импортирую ли я один из этих соответствующих модулей в views.py по очереди. Это часто дает вам виновника.

Распространенным местом, где это может пойти не так, является ваш models.py. Часто главное в том, что вы делаете. Но убедитесь, что вы стараетесь, чтобы ваши импорты указывали на AT models.py, а не подальше от него. Поэтому импортируйте модели из views.py, но не наоборот.

И в urls.py я обычно импортирую свои представления (потому что я получаю приятную немедленную ошибку импорта, когда я делаю ошибку таким образом). Но чтобы избежать ошибок кругового импорта, вы также можете ссылаться на свои представления с помощью пунктирной строки пути. Но это зависит от того, что вы делаете в своем urls.py.

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

И сделайте ваш импорт абсолютным, а не относительным. Я имею в виду «из myproject.views import xyz» вместо «из представлений import xyz». Делая его абсолютным в сочетании с сортировкой списка импорта, ваш импорт становится более четким и аккуратным.

6 голосов
/ 10 февраля 2012

Просто преобразовав комментарий выше в ответ ...

Если у вас есть циклический импорт, python -vv добьется цели.Другим способом было бы перегрузить загрузчик модулей (где-то есть ссылка, но я не могу найти ее сейчас).Обновление: вы, вероятно, можете сделать это с помощью ModuleFinder

Сбой без вывода сообщений происходит из-за того, что у вас есть несколько модулей с одинаковым именем.Тогда порядок импорта python (основанный на pythonpath) является ссылкой.О, когда / если вы измените имя, убедитесь, что вы тоже удалили .pyc :) (это случилось со мной несколько раз)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...