Решение проблемы циклического импорта в Python 3.4/3.5
Быстрый ответ
Для предотвращения циклических импортов в процессе работы с описанием типов используйте технику "forward references", обрамляя типы в кавычки. С версии Python 3.7 становится доступной директива from __future__ import annotations
, позволяющая отложить разрешение аннотаций типов, что помогает решить проблему с цикличностью.
Смотрите пример кода:
# файл: 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
и абстрактные базовые классы для решения этих вопросов в более сложных случаях.
Борьба с циклическими импортами при помощи TYPE_CHECKING
При наличии сложных зависимостей между классами можно использовать TYPE_CHECKING
. При этом декларация зависимостей происходит только для целей проверки типов:
# файл: some_module.py
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from other_module import OtherClass
class SomeClass:
def some_method(self) -> 'OtherClass':
...
Застосування абстрактных базовых классов (ABC)
Абстрактные базовые классы предоставляют способ определения интерфейса, не требуя прямого взаимодействия между классами. Это помогает снизить объем взаимосвязанности и избежать преждевременных импортов.
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
, которая обеспечивает отсроченное разрешение типов, исключая прямые импорты во время выполнения.
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
:
TYPE_CHECKING = False
if TYPE_CHECKING:
from other_module import OtherClass
Такой подход позволяет анализаторам типов, как, например, mypy использовать конструкции проверки типов, не влияя на производительность приложения.
Локализация импортов в области видимости методов
Ещё одним способом обхода циклических зависимостей является импорт прямо в теле метода:
class D:
def method_showing_import(self):
from e import E
return E().another_method()
Стратегии написания поддерживаемого кода
Отдача предпочтения интерфейсам вместо конкретных реализаций
Предпочтение интерфейса прямому взаимодействию между классами А и В приводит к снижению уровня связности и увеличению гибкости структуры кода.
Развитие адаптивных миксинов
Настоятельно рекомендовано структурирование миксинов с целью использования интерфейсов или абстрактных классов и определения зависимостей с помощью абстрактных методов.
Подход к импорту модуля в целом
Лучше импортировать всей модуль, а не отдельные классы из него. Это часто помогает предотвратить возникновение циклических зависимостей.
Визуализация
Представим схему зависимостей, где каждый узел — это модуль.
🔗 Модуль A ----(импорт)------⚡------(обходной путь)------> 🔗 Модуль B
|
(TYPE_CHECKING)
|
🔗 Модуль C <---(подсказка типа)---⚡<---(отсутствие прямого импорта)--- 🔗 Модуль D
Цель — внедрить Type Hinting в структуру зависимостей, избегая циклических импортов.
Расшифровка:
- ⚡: Обходные пути с использованием
"ForwardRef"
илиTYPE_CHECKING
. - Прямые связи (импорты) обозначают циклические импорты, которых мы хотим избежать.
- Косвенные пути предполагают дефинирование типов, без создания цикличных связей.
Полезные материалы
- PEP 484 – Подсказки типов — базовые принципы описания типов в Python.
- typing — Поддержка подсказок типов — рекомендации по применению "предварительных ссылок" для обхода циклических импортов.
- Типизация в Python (руководство) – Real Python — полное руководство по внедрению подсказок типов в приложения на Python.
- PEP 563 – Отложенная оценка аннотаций — разбор техники отложенных аннотаций типов в вопросах решения циклических импортов.
- Циклические импорты в Python — анализ проблем, связанных с циклическими импортами в Python, и вариантов их решения.
- mypy – Примеры — обучение использованию mypy, инструмента для предотвращения циклических зависимостей в Python.
- 5. Система импорта — Документация Python 3.12.2 — детальный анализ системы импорта Python, важной для понимания механизма работы циклических импортов.