Решение проблемы циклического импорта в Python 3.4/3.5

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Для предотвращения циклических импортов в процессе работы с описанием типов используйте технику "forward references", обрамляя типы в кавычки. С версии Python 3.7 становится доступной директива from __future__ import annotations, позволяющая отложить разрешение аннотаций типов, что помогает решить проблему с цикличностью.

Смотрите пример кода:

Python
Скопировать код
# файл: a.py
from __future__ import annotations

class A:
    def method(self) -> 'B':
        ...

# файл: b.py

class B:
    def method_returning_a(self) -> 'A':
        ...

Здесь 'B' и 'A' представляют собой "forward references", что предотвращает возникновение проблем с импортированием. Благодаря использованию from __future__ import annotations аннотации остаются в виде строк и не требуют немедленного интерпретирования.

Впоследствии мы рассмотрим методы такие как TYPE_CHECKING и абстрактные базовые классы для решения этих вопросов в более сложных случаях.

Кинга Идем в IT: пошаговый план для смены профессии

Борьба с циклическими импортами при помощи TYPE_CHECKING

При наличии сложных зависимостей между классами можно использовать TYPE_CHECKING. При этом декларация зависимостей происходит только для целей проверки типов:

Python
Скопировать код
# файл: some_module.py
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from other_module import OtherClass

class SomeClass:
    def some_method(self) -> 'OtherClass':
        ...

Застосування абстрактных базовых классов (ABC)

Абстрактные базовые классы предоставляют способ определения интерфейса, не требуя прямого взаимодействия между классами. Это помогает снизить объем взаимосвязанности и избежать преждевременных импортов.

Python
Скопировать код
from abc import ABC, abstractmethod

class UnpredictableMixin(ABC):
    @abstractmethod
    def surprise_element(self) -> 'UnpredictableResult':
        pass

Обход циклических импортов в Python 3.5 и более ранних версиях

Python 3.5 и 3.6: применение модуля typing

Для Python версий 3.5 и 3.6 можно использовать технику "lazy evaluation" в модуле typing, которая обеспечивает отсроченное разрешение типов, исключая прямые импорты во время выполнения.

Python
Скопировать код
from typing import get_type_hints

class A:
    def method(self) -> 'B':
        ...

def get_b_type():
    return get_type_hints(A.method)['return_value']

Python 3.4 и ниже: ручное управление TYPE_CHECKING

В версиях Python до появления __future__ приходилось самостоятельно определить TYPE_CHECKING:

Python
Скопировать код
TYPE_CHECKING = False

if TYPE_CHECKING:
    from other_module import OtherClass

Такой подход позволяет анализаторам типов, как, например, mypy использовать конструкции проверки типов, не влияя на производительность приложения.

Локализация импортов в области видимости методов

Ещё одним способом обхода циклических зависимостей является импорт прямо в теле метода:

Python
Скопировать код
class D:
    def method_showing_import(self):
        from e import E
        return E().another_method()

Стратегии написания поддерживаемого кода

Отдача предпочтения интерфейсам вместо конкретных реализаций

Предпочтение интерфейса прямому взаимодействию между классами А и В приводит к снижению уровня связности и увеличению гибкости структуры кода.

Развитие адаптивных миксинов

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

Подход к импорту модуля в целом

Лучше импортировать всей модуль, а не отдельные классы из него. Это часто помогает предотвратить возникновение циклических зависимостей.

Визуализация

Markdown
Скопировать код
Представим схему зависимостей, где каждый узел — это модуль.

🔗 Модуль A ----(импорт)------⚡------(обходной путь)------> 🔗 Модуль B
                     |
                (TYPE_CHECKING)
                     |
🔗 Модуль C <---(подсказка типа)---⚡<---(отсутствие прямого импорта)--- 🔗 Модуль D

Цель — внедрить Type Hinting в структуру зависимостей, избегая циклических импортов.

Расшифровка:

  • ⚡: Обходные пути с использованием "ForwardRef" или TYPE_CHECKING.
  • Прямые связи (импорты) обозначают циклические импорты, которых мы хотим избежать.
  • Косвенные пути предполагают дефинирование типов, без создания цикличных связей.

Полезные материалы

  1. PEP 484 – Подсказки типов — базовые принципы описания типов в Python.
  2. typing — Поддержка подсказок типов — рекомендации по применению "предварительных ссылок" для обхода циклических импортов.
  3. Типизация в Python (руководство) – Real Pythonполное руководство по внедрению подсказок типов в приложения на Python.
  4. PEP 563 – Отложенная оценка аннотаций — разбор техники отложенных аннотаций типов в вопросах решения циклических импортов.
  5. Циклические импорты в Python — анализ проблем, связанных с циклическими импортами в Python, и вариантов их решения.
  6. mypy – Примеры — обучение использованию mypy, инструмента для предотвращения циклических зависимостей в Python.
  7. 5. Система импорта — Документация Python 3.12.2 — детальный анализ системы импорта Python, важной для понимания механизма работы циклических импортов.