Подтвердить что ты не робот

Инструмент для точного определения кругового импорта в Python/Django?

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

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

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

Чтобы разработать немного...

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

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

но в Интернете выбрасывается ошибка:

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

С видимым отсутствием нескольких полей.

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

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

4b9b3361

Ответ 1

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

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

Общие причины в Django:

  • Импортирование подпакета из совершенно другого модуля,

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

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

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

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

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

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

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

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

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

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

  • Слишком много в __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 может быть легко импортирована внутри функции вместо глобальной.

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

Ответ 2

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

# from myapp import MyAppModel  ## removed circular import

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

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

Ответ 3

То, что я обычно делаю, когда сталкиваюсь с ошибкой импорта, - это работать обратным образом. Я бы получил ошибку "не могу импортировать xyz из myproject.views", хотя xyz существует просто отлично. Затем я делаю две вещи:

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

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

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

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

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

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

Ответ 4

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

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

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