ImportError: cannot import name в Python — циклический и относительный импорт

Python Автор: Среда и версия: CPython 3.14.5
содержание

ImportError: cannot import name 'User' from 'app.models' означает, что Python нашёл и начал загружать app.models, но не смог получить из него имя User. Обычно имя удалили, переименовали, не экспортировали из __init__.py или ещё не успели создать из-за циклического импорта.

Если в последней строке написано ModuleNotFoundError: No module named 'app', это другая ветка диагностики: сам модуль не найден. Проверка venv, sys.path и установки пакета разобрана в инструкции по ModuleNotFoundError. Не запускайте pip install только потому, что видите cannot import name.

Python / 01

Цикл импорта застал модуль недописанным

Цикл импорта застал модуль недописанным01 main импортирует a a → import b Определение User в a ещё не выполнено. 02 b обращается назад from a import User a уже в sys.modules, но инициализирован лишь частично. 03 Разорви цикл общие определения → c Вынеси общую зависимость или перенеси импорт в нужный момент.01main импортирует aa → import bОпределение User в a ещё не выполнено.02b обращается назадfrom a import Usera уже в sys.modules, но инициализирован лишь частично.03Разорви циклобщие определения → cВынеси общую зависимость или перенеси импорт в нужный момент.
При цикле A↔B модуль a уже есть в sys.modules, но его код ещё не дошёл до class User. Поэтому обратный импорт видит модуль, но не имя.

Чем ImportError отличается от ModuleNotFoundError

ModuleNotFoundError — потомок ImportError, но сообщения описывают разные этапы импорта:

ModuleNotFoundError: No module named 'reports'

Python не смог найти reports по путям импорта. Сначала проверяют окружение, имя модуля и sys.path.

ImportError: cannot import name 'render_pdf' from 'reports' (/project/reports.py)

reports найден, и Python перешёл к поиску render_pdf в нём, но не нашёл это имя. Здесь проверяют имя, версию API, файл загруженного модуля и возможный цикл.

Точный путь в скобках особенно полезен: он показывает, какой файл Python принял за reports.

Проверьте, есть ли имя в модуле

Допустим, раньше библиотека экспортировала Client, а после обновления класс переименовали в ApiClient. Тогда старый импорт падает, хотя сам пакет установлен:

from service import Client

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

python -c "import service; print(service.__file__); print(hasattr(service, 'Client')); print([name for name in dir(service) if 'Client' in name])"

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

Если имя живёт в подмодуле, импорт должен вести туда:

service/
├── __init__.py
└── client.py  # здесь объявлен ApiClient
from service.client import ApiClient

Вариант from service import ApiClient сработает, только если service/__init__.py сам свяжет это имя, например from .client import ApiClient.

Что на самом деле делает all

__all__ управляет набором публичных имён для импорта со звёздочкой:

# api.py
__all__ = ["public_name"]

public_name = 1
internal_name = 2
from api import *

print(public_name)  # 1
# internal_name здесь не связано

Но __all__ не запрещает явный импорт:

from api import internal_name

print(internal_name)  # 2

Поэтому добавлять существующее имя в __all__ ради исправления from api import internal_name не нужно. Если явный импорт падает, атрибут либо не связан в модуле, либо загрузка не дошла до его объявления.

Как возникает partially initialized module

Минимальный цикл состоит из трёх файлов:

cycle_demo/
├── main.py
├── a.py
└── b.py
# main.py
from a import User

print(User("Ada"))
# a.py
from b import format_name


class User:
    def __init__(self, name):
        self.name = format_name(name)
# b.py
from a import User


def format_name(name):
    return name.upper()

Запуск из cycle_demo:

python main.py

заканчивается строкой:

ImportError: cannot import name 'User' from 'a' (.../a.py)

Порядок выполнения важен:

  1. main.py запрашивает User из a.
  2. Python создаёт объект модуля a, записывает его в sys.modules и только потом начинает выполнять a.py сверху вниз.
  3. Первая строка a.py начинает загрузку b.py.
  4. Первая строка b.py снова импортирует a. Python находит его в sys.modules и не запускает a.py второй раз.
  5. Имя User ещё не создано: выполнение a.py остановилось на первой строке, а class User стоит ниже. Поэтому from a import User поднимает ImportError.

CPython может добавить фразу partially initialized module, особенно для цикла внутри пакета, но этот диагностический суффикс не гарантирован. Проверяйте весь traceback: цепочка может проходить не напрямую A↔B, а через A→B→C→A.

Три способа убрать циклический импорт

1. Вынести общую зависимость в третий модуль

Это основное исправление, если A и B нужен один и тот же тип, константа или функция. Оба модуля зависят от общего, а не друг от друга:

app/
├── models.py
├── formatter.py
└── main.py
# models.py
class User:
    def __init__(self, name):
        self.name = name
# formatter.py
from models import User


def format_user(user: User):
    return user.name.upper()
# main.py
from formatter import format_user
from models import User

print(format_user(User("Ada")))

Теперь граф зависимостей однонаправлен: main → formatter → models и main → models.

2. Импортировать модуль, а не имя

from a import User требует User прямо во время импорта. import a может разорвать эту конкретную гонку, если код обратится к a.User только после завершения обоих модулей:

# a.py
import b


class User:
    def __init__(self, name):
        self.name = b.format_name(name)
# b.py
import a


def format_name(name):
    return name.upper()


def is_user(value):
    return isinstance(value, a.User)

Тела функций выполнятся позже, когда a.py уже создал User. Если b.py попытается вычислить a.User на верхнем уровне, замена формы импорта не поможет: вместо ImportError может появиться AttributeError.

3. Перенести импорт внутрь функции

Локальный импорт откладывает загрузку до вызова функции:

def build_report(data):
    from .formatter import format_report

    return format_report(data)

К этому моменту другие модули могут уже завершить инициализацию. Но зависимость становится менее заметной, а ошибка импорта переносится из старта программы в конкретный вызов. Используйте этот вариант как осознанный компромисс для редкого пути или старого кода. Если два модуля по-прежнему знают друг о друге, лучше вынести общую часть.

Относительный импорт без контекста пакета

Другое типичное сообщение выглядит так:

ImportError: attempted relative import with no known parent package

Оно воспроизводится, если запустить файл пакета напряму:

shop-project/
└── shop/
    ├── __init__.py
    ├── cli.py
    └── prices.py
# shop/prices.py
def total():
    return 120
# shop/cli.py
from .prices import total

print("package:", __package__)
print("spec:", __spec__.name)
print("total:", total())

Неверная команда:

python shop/cli.py

При прямом запуске файл становится модулем __main__, но интерпретатор не знает его полного пакетного имени. Для скрипта, заданного путём к файлу, __spec__ и __package__ на CPython 3.14.5 равны None. Ведущая точка в .prices не имеет точки отсчёта.

Запустите модуль из shop-project — корня проекта, внутри которого лежит пакет shop:

cd shop-project
python -m shop.cli

Результат:

package: shop
spec: shop.cli
total: 120

Ключ -m ищет shop.cli как модуль и сохраняет его пакетный контекст. В CPython 3.14 относительный импорт опирается прежде всего на __spec__.parent; __package__ ещё служит запасным механизмом, но этот путь устарел. Не присваивайте эти атрибуты вручную ради обхода ошибки: команда python -m package.module описывает намерение явно и не зависит от внутреннего запасного пути.

Проверьте, не затенил ли локальный файл библиотеку

При обычном запуске Python добавляет в начало пути поиска каталог скрипта, а для python -m — текущий каталог. Поэтому файл проекта может перехватить имя стандартной или сторонней библиотеки. Ключи -P и -I отключают это поведение:

project/
├── main.py
└── requests.py  # локальный файл
# main.py
from requests import Session

Если в локальном requests.py нет Session, импорт покажет cannot import name, хотя нужный дистрибутив может быть установлен. Найдите первый подходящий файл без выполнения его кода:

python -c "import importlib.util; print(importlib.util.find_spec('requests').origin)"

Или посмотрите файл уже импортированного модуля:

python -c "import requests; print(requests.__file__)"

Путь вида /project/requests.py подтверждает затенение. Переименуйте локальный файл вроде http_client.py и повторите команду. Так же проверяют json.py, typing.py, email.py, logging.py, random.py и каталоги с такими именами.

Короткий алгоритм диагностики

  1. Прочитайте последнюю строку и весь traceback. No module named и cannot import name требуют разных проверок.
  2. Посмотрите путь в скобках или напечатайте module.__file__. Неожиданный путь выдаёт затенение или другое окружение.
  3. Проверьте точное написание имени, модуль, где оно объявлено, и API установленной версии. Помните, что __all__ ограничивает import *, а не явный from module import name.
  4. Если есть partially initialized module, пройдите цепочку файлов в traceback и найдите обратное ребро. Лучший фикс — общий модуль без зависимости от A и B.
  5. Если видите attempted relative import with no known parent package, запустите python -m package.module из каталога, который содержит пакет.

Перехватывать ImportError ради продолжения запуска обычно не нужно: без обязательного модуля программа остаётся в частично известном состоянии. Исключение — явно необязательная зависимость с полноценной запасной веткой. Если нужно обрабатывать такую ситуацию, перехватывайте узко и учитывайте, что ModuleNotFoundError наследуется от ImportError. Базовые правила перехвата разобраны в статье про try/except.

Источники