2025, Oct 04 21:16
Ошибка mypy «Source file found twice»: причины и три способа решения
Разбираем ошибку mypy «Source file found twice» в Python: почему файл видится под разными именами модулей и как исправить импорты, пакеты и запуск mypy.
Когда mypy сообщает «Source file found twice under different module names», это означает, что один и тот же файл Python индексируется по двум путям импорта. Такое часто случается в проектах, где каталог вроде testing не является полноценным пакетом, но код импортируется так, будто это пакет. В итоге у модуля появляются два разных имени, и проверка типов останавливается с ошибкой.
Минимальный пример, воспроизводящий проблему
Ниже — ситуация, повторяющая суть проблемы. Директория testing просматривается рекурсивно, внутри есть поддерево пакета, но сама testing пакетом не является.
mkdir -p testing/pkgcore
touch testing/pkgcore/{__init__,modutil}.py  # файл testing/__init__.py намеренно отсутствует
echo "from testing.pkgcore import modutil" > testing/test_modutil.py
mypy testing
mypy отвечает ошибкой о дублирующемся источнике:
testing/pkgcore/modutil.py: error: Source file found twice under different module names: "modutil" and "testing.pkgcore.modutil"
testing/pkgcore/modutil.py: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules for more info
testing/pkgcore/modutil.py: note: Common resolutions include: a) adding `__init__.py` somewhere, b) using `--explicit-package-bases` or adjusting MYPYPATH
Found 1 error in 1 file (errors prevented further checking)
Что на самом деле происходит
Команда mypy testing заставляет mypy обойти папку testing и вывести структуру пакетов по наличию файлов __init__.py. Первый каталог с __init__.py считается корневым пакетом. Все, что ниже, получает имена относительно этого корня. В нашем примере в testing/pkgcore есть __init__.py, поэтому mypy сопоставляет testing/pkgcore/modutil.py модулю с именем pkgcore.modutil.
Проблема в том, что в test_modutil.py используется импорт from testing.pkgcore import modutil. Такое утверждение подразумевает, что testing — это пакет, что противоречит способу, которым mypy нашёл модуль. В результате mypy видит один и тот же файл дважды: как pkgcore.modutil и как testing.pkgcore.modutil. Именно это несоответствие и вызывает ошибку о дублирующемся источнике.
Тот же шаблон проявился и в реальном случае: файл testing/example_scripts/rewrite/tests/test_main.py делал импорт from testing.example_scripts.rewrite.src.main import func, тогда как обнаружение mypy относило testing/example_scripts/rewrite/src/main.py к другому корню, из‑за чего файл фигурировал под двумя разными именами.
Как это исправить
Есть три простых способа согласовать пути импорта с тем, как mypy обнаруживает модули. Выберите тот, который подходит под структуру вашего проекта и используемые инструменты.
Во‑первых, подстройте импорты под то, как mypy называет найденный модуль. Если mypy рассматривает testing/pkgcore/modutil.py как pkgcore.modutil, то импорт через testing и порождает дублирование. Переключение импорта на обнаруженное имя снимает конфликт.
echo "from pkgcore import modutil" > testing/test_modutil.py
mypy testing
# Успех: проблем не найдено в 3 исходных файлах
Этот вариант подчёркивает, что testing — часть исходной структуры, а не пакет. Тесты остаются в отдельной папке, а код импортируется по фактическому имени пакета. Другим инструментам может понадобиться поправить sys.path, чтобы testing попадал в путь поиска модулей во время их запуска.
Во‑вторых, запустите mypy как проверку, понимающую пакеты. Флаг -p явно сообщает mypy, что testing — это пространство имён пакета. С этой подсказкой mypy сопоставит testing/pkgcore/modutil.py модулю testing.pkgcore.modutil, что совпадёт с вашими импортами.
mypy -p testing
# Успех: проблем не найдено в 4 исходных файлах
В‑третьих, превратите testing в пакет, добавив в корень __init__.py. Как только testing станет пакетом, mypy будет назначать имена модулей, начиная с testing, и импорты вида from testing.pkgcore import modutil снова начнут совпадать.
touch testing/__init__.py
mypy testing
# Успех: проблем не найдено в 4 исходных файлах
Почему это важно
Единообразие идентичности модулей — основа статического анализа. Если к одному и тому же файлу можно добраться по разным путям импорта, проверяющие видят два несвязанных модуля с одинаковым содержимым. Это ломает инкрементальный анализ, инвалидирует кэши и способно скрыть реальные ошибки за потоком сбоев о дублирующемся источнике. И не только mypy: такая несогласованность может проявиться и во время выполнения, вызывая неожиданное поведение импорта в тестах и инструментах.
Итоги
Запуская mypy по дереву каталогов, учитывайте, как он сопоставляет пути файлов именам модулей. Либо импортируйте код под теми именами, которые mypy выводит сам, либо вызывайте mypy так, чтобы явно задать границы пакетов, либо добавьте недостающий __init__.py и оформите пакет. Если столкнулись с сообщением о дублирующемся источнике, в выводе mypy есть полезные подсказки — включая документацию по сопоставлению путей и имен модулей и советы вроде добавления __init__.py, использования --explicit-package-bases или настройки MYPYPATH. Согласованный граф импортов и структура пакетов избавляют от подобных конфликтов и делают проверку типов предсказуемой.