Как победить циклические импорты в Python простым способом

от автора

Команда Python for Devs подготовила перевод статьи о том, как справляться с циклическими импортами в Python. В статье показан простой приём: иногда не нужно переписывать архитектуру, а достаточно изменить стиль импорта, чтобы избежать ошибок.


Циклические импорты в Python могут запутать. Иногда достаточно просто изменить форму импорта, чтобы устранить проблему.

В Python циклический импорт возникает, когда два файла пытаются импортировать друг друга. В результате один из модулей оказывается не до конца инициализирован, и всё ломается. Лучший способ исправить такую ситуацию — организовать код слоями, чтобы зависимости при импортах шли только в одну сторону. Но иногда помогает просто поменять стиль import. Сейчас покажу.

Предположим, у нас есть такие файлы:

# one.py from two import func_two  def func_one():     func_two() 
# two.py from one import func_one  def do_work():     func_one()  def func_two():     print("Hello, world!") 
# main.py from two import do_work do_work() 

Если запустить main.py, получим следующее:

% python main.py Traceback (most recent call last):   File "main.py", line 2, in <module>     from two import do_work   File "two.py", line 2, in <module>     from one import func_one   File "one.py", line 2, in <module>     from two import func_two ImportError: cannot import name 'func_two' from partially initialized   module 'two' (most likely due to a circular import) (two.py) 

Когда Python импортирует модуль, он выполняет файл построчно. Все глобальные объекты (имена верхнего уровня — включая функции и классы) становятся атрибутами объекта модуля, который в данный момент создаётся. В two.py на второй строке мы делаем импорт из one.py. В этот момент модуль two уже создан, но у него пока нет никаких атрибутов — ведь ещё ничего не определено. В нём появятся do_work и func_two, но до этого мы не дошли: инструкции def ещё не выполнены, значит, функций просто не существует. Как и при вызове функции, когда срабатывает import, начинается исполнение импортируемого файла, и до текущего файла управление не возвращается, пока импорт не завершится.

Импорт one.py начинается, и на второй строке он пытается получить имя из модуля two. Как мы уже говорили, модуль two существует, но у него пока нет определённых имён. Это и вызывает ошибку.

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

# one.py import two              # раньше: from two import func_two  def func_one():     two.func_two()      # раньше: func_two() 
# two.py import one              # раньше: from one import func_one  def do_work():     one.func_one()      # раньше: func_one()  def func_two():     print("Hello, world!") 
# main.py from two import do_work do_work() 

Если запустить исправленный код, получится:

% python main.py Hello, world! 

Теперь всё работает, потому что в two.py мы импортируем one на второй строке, а в one.py — импортируем two тоже на второй строке. Это не мешает, ведь модуль two уже существует. Он всё ещё пустой, как и раньше, но теперь мы не пытаемся во время импорта найти в нём имя, которого ещё нет. Когда все импорты завершаются, и one, и two получают все свои определения, и мы можем спокойно обращаться к ним внутри функций.

Ключевая идея здесь в том, что конструкция from two import func_two пытается найти func_two во время импорта, когда этой функции ещё не существует. Перенос поиска имени в тело функции с помощью import twoпозволяет всем модулям полностью инициализироваться до того, как мы попытаемся их использовать, что и устраняет ошибку циклического импорта.

Как я упоминал в начале, лучший способ решить проблему циклических импортов — это организовать код так, чтобы модули не зависели друг от друга. Но сделать это бывает непросто, и такой приём может помочь выиграть время и снова заставить код работать.

Русскоязычное сообщество про Python

Друзья! Эту статью перевела команда Python for Devs — канала, где каждый день выходят самые свежие и полезные материалы о Python и его экосистеме. Подписывайтесь, чтобы ничего не пропустить!


ссылка на оригинал статьи https://habr.com/ru/articles/944684/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *