Модуль циклического импорта
В моем проекте я использую модуль interaction
который содержит несколько "интерфейсов" для доступа к системным функциям. Например, у меня есть 2 модуля для реализации различных интерфейсов, и один из них зависит от другого интерфейса.
module1.py:
value = 5
def init():
pass
def provide_int():
global value
return value
module2.py:
def init():
import interaction
global value
value = str(interaction.int_provider.provide_int())
def provide_string():
global value
return value
Поэтому я хочу использовать это так же:
Interaction.py:
from importlib import import_module
globals()['int_provider'] = import_module('module1')
globals()['int_provider'].init()
globals()['str_provider'] = import_module('module2')
globals()['str_provider'].init()
Таким образом, во время инициализации module2
(вызывая функцию init) Я получаю ImportError из-за циклического импорта. Этот пример, конечно, синтетический, но общее состояние одно и то же.
У меня есть 2 вопроса:
1. Это правильный способ использовать "глобальный" модуль, как мой interaction.py
проксирование интерфейсов?
2. Как я могу победить этот циклический импорт?
2 ответа
Я предлагаю покончить с init
функции и делает provide_string()
Функция устанавливает свои значения при первом вызове. Поскольку во время загрузки модуля происходит очень мало, не будет проблем с модулями, ссылающимися друг на друга. Кроме того, вы можете упростить interaction
модуль с помощью as
ключевое слово с регулярным импортом:
iteraction.py:
import module1 as int_provider
import module2 as string_provider
module1.py:
value = 5
def provide_int():
return value # no global statement necessary, since we don't assign to value
module2.py:
value = None # this will be set up after the first call to provide_string
def provide_string():
global value # global is needed, since we may assign a new string to value
if value is None:
import interaction
value = str(interaction.int_provider.provide_int())
return value
Как я могу победить этот циклический импорт?
Вы можете использовать лениво созданные провайдеры, используя что-то вроде django.utils.functional.SimpleLazyObject. Таким образом, вы инициализируете провайдера в момент обращения к любому его атрибуту. Вы должны будете помнить, чтобы запомнить результат в SimpleLazyObject
так как переданная функция будетпереоцениваться каждый раз.
Использование:
def init_str_provider():
if not hasattr(init_str_provider, 'provider'):
init_str_provider.provider = import_module('module2')
init_str_provider.provider.init()
return init_str_provider.provider
str_provider = SimpleLazyObject(init_str_provider)
Это правильный способ использовать "глобальный" модуль, как мойinteraction.py
проксирование интерфейсов?
Если бы я должен был создать такую структуру с интерфейсами, я бы предпочел пойти с:
- Метод регистрации класса провайдера в
interfaces.py
, - Использование классов для представления поставщика (упрощение взаимодействия благодаря наследованию!).
- Обеспечить
get_provider(name)
функция вinterfaces.py
разрешить ленивую инициализацию провайдера.
Такое разделение облегчает тестирование и макет отдельных провайдеров и оставляет больше места для настройки. Кроме того, если вам когда-либо понадобится расширить поведение одного провайдера внутри другого, объекты класса сохраняют свое состояние внутри себя, а не на уровне модуля. Таким образом, вы на самом деле можете наследовать от других поставщиков, не беспокоясь о том, что любой дочерний поставщик будет работать с внутренними компонентами автономного базового поставщика.