Как Python выбирает модуль при импорте, если одинаковое имя есть в нескольких каталогах?
В обычном импорте Python проверяет источники в порядке, заданном механизмом импорта, прежде всего — элементы sys.path. Первый подходящий модуль или пакет обычно становится результатом поиска; поэтому каталог, стоящий раньше, может скрыть одноимённый модуль из другого каталога.
Уже загруженный модуль повторно не ищется: Python берёт его из sys.modules. Поэтому изменение порядка каталогов после первого импорта само по себе обычно не меняет используемый объект модуля.
Механизм поиска по путям появился как практическая основа модульной системы: код нужно было разделять на файлы, подключать библиотеки из разных каталогов и переиспользовать один и тот же импортируемый компонент в разных программах.
Такой подход сделал размещение модулей гибким, но создал компромисс: одинаковые имена в разных источниках могут приводить к выбору неожиданного модуля. Поэтому структура каталогов и порядок путей являются частью поведения приложения.
Если в рабочем каталоге есть файл с именем стандартной или сторонней библиотеки, он может быть найден раньше настоящей зависимости. В результате импорт завершится успешно, но программа получит объект с другим API или неожиданной логикой.
Проблема особенно опасна при наличии файлов вроде json.py, logging.py или requests.py в каталоге проекта. Ошибка может проявиться только на отдельной машине, потому что sys.path зависит от способа запуска, окружения, виртуального окружения и настроек интерпретатора.
Импорт проходит через систему загрузчиков. Сначала Python использует поисковые механизмы импорта, включая sys.meta_path; для обычных файловых путей работает механизм, связанный с элементами sys.path. Он проверяет очередной источник по поддерживаемым правилам: это могут быть файлы модулей, каталоги пакетов, расширения и другие форматы.
Порядок элементов sys.path имеет значение. В него обычно входят путь запускаемого скрипта или текущий каталог для некоторых способов запуска, пользовательские пути вроде PYTHONPATH, стандартные каталоги и каталоги окружения, но точный порядок зависит от режима запуска и конфигурации Python.
После успешной загрузки объект модуля помещается в sys.modules под именем импорта. Последующий импорт того же имени использует эту запись, не выполняя новый поиск и не создавая второй экземпляр модуля.
Очистка записи из sys.modules и повторный импорт может запустить поиск заново, но это не универсальный способ перезагрузки: другие части программы могут продолжать ссылаться на старый объект модуля и его классы. Для анализа источника можно использовать средства инспекции импорта, например importlib.util.find_spec, а для исправления проблемы — устранить конфликт имён и проверить окружение.
Минимальный пример показывает, что первым выбирается каталог, расположенный раньше в sys.path:
Здесь second добавлен перед first, поэтому найден файл second/target.py. В реальном проекте безопаснее не изменять sys.path вручную без необходимости, использовать виртуальное окружение и избегать имён, совпадающих с именами зависимостей или стандартных модулей.
В проекте появился файл email.py. Локальные тесты, запускаемые из корня проекта, начали импортировать этот файл вместо стандартного пакета email. Переименование файла решило проблему без изменения логики приложения.
Рассматривались два варианта. Можно было вручную переставить элементы sys.path, но это создало бы скрытую зависимость от порядка запуска и ухудшило предсказуемость. Можно было явно переопределить загрузчик импорта, но это избыточно для обычного конфликта имён и усложняет сопровождение.
Выбранное решение — переименовать конфликтующий модуль, удалить остаточные файлы кэша и проверить импорт в чистом виртуальном окружении. Такой вариант устраняет причину, одинаково работает в разработке и CI и не требует нестандартных правил импорта.
Нет. Если модуль уже есть в sys.modules, обычный импорт использует кэшированную запись. Для нового поиска нужно удалить соответствующую запись из sys.modules, а для некоторых источников дополнительно вызвать importlib.invalidate_caches(), но повторная загрузка может оставить в программе ссылки на старую версию объекта.
Если один и тот же файл доступен через разные пути или пакетные имена, система импорта может создать разные записи в sys.modules. Тогда код из файла потенциально выполнится дважды, а классы, созданные при этих загрузках, будут считаться разными типами, даже если их исходный текст совпадает.
Нет. Он зависит от режима запуска, переменных окружения, виртуального окружения, параметров интерпретатора и подключённых импорт-хуков. Поэтому приложение не должно полагаться на случайное перекрытие модулей; надёжнее использовать уникальные имена, корректную упаковку проекта и проверяемые зависимости.