Importação Circular em Python: Como Corrigir (Com Exemplos)
Publicado em
Atualizado em
Uma importação circular (circular import) acontece quando o módulo A importa o módulo B e B (direta ou indiretamente) também importa A. O Python não fica preso em um loop infinito na maioria dos casos. O que você vê é uma importação parcial: um módulo ainda está carregando quando o outro tenta ler um nome dele — e aí surge ImportError / AttributeError, ou objetos só pela metade.
Correção rápida (comece aqui)
| Situação | O que fazer primeiro |
|---|---|
| Dois módulos se importam no topo do arquivo | Mova o código compartilhado para um terceiro módulo que os dois possam importar |
| Você só precisa de um nome dentro de uma função | Importe dentro dessa função (lazy import) |
| Você só precisa de tipos para anotações | Use from typing import TYPE_CHECKING e aspas nas anotações se precisar |
| Pacote grande com ciclos profundos | Redesene as dependências para que camadas baixas nunca importem camadas altas |
Padrão mínimo que costuma destravar na hora:
# Instead of a top-level cycle:
# models.py imports services.py
# services.py imports models.py
# services.py — import only where used
def process_user(user_id: int):
from models import User # lazy import breaks the load-time cycle
return User.get(user_id)Se isso parecer gambiarra, continue lendo. A correção duradoura quase sempre é direção de dependência, não um truque esperto de import.
- Runcell Science: alternativa open source ao Claude Science para pesquisa com IA
- Como impedir o Mac de dormir: mantenha Codex, Claude Code e agentes de IA rodando
- OpenClaw vs ZeroClaw vs Pi Agent vs Nanobot: qual stack de agentes de IA você deve escolher em 2026?
- Como o Claude Code analisa notebooks Jupyter em Data Science: capacidade real, limites e alternativa melhor
- Claude Code Routines: rotinas e cron jobs para agentes de IA
- Claude Code Desktop: como ativar Bypass permissions
- Como Construir Dois Agentes Python com o Protocolo A2A do Google - Tutorial Passo a Passo
- Top 10 bibliotecas de visualização de dados em Python em 2025
Como uma importação circular se manifesta
Exemplo mínimo que falha
Crie dois módulos irmãos:
# a.py
import b
def hello_from_a():
return "A"
print("a loaded", b.hello_from_b())# b.py
import a
def hello_from_b():
return "B"
print("b loaded", a.hello_from_a())Execute:
python a.pyFalha típica (o texto varia um pouco entre versões do Python):
ImportError: cannot import name 'hello_from_a' from partially initialized module 'a'
(most likely due to a circular import)Essa mensagem é a forma de busca desse problema. O Python começou a carregar a, que começou a carregar b, que tentou terminar a importação de a antes de a terminar de executar.
O que realmente dá errado
Quando o Python importa um módulo:
- Cria um objeto de módulo vazio e coloca em
sys.modules. - Executa o corpo do módulo de cima para baixo.
- Só depois que esse corpo termina é que todos os nomes de nível superior têm garantia de existir.
Durante um ciclo, o passo 2 do módulo A ainda está rodando quando B pede algo definido mais abaixo em A. O nome não existe → erro de importação. É uma falha de inicialização parcial (partially initialized module), não um loop sem fim.
Fluxo de diagnóstico
Siga esta ordem. Pare quando o ciclo sumir.
-
Reproduza com o mesmo ponto de entrada
Rode o mesmo arquivo que o erro menciona (python -m package.moduleou o caminho do script). Importação circular é sensível ao entrypoint. -
Leia o traceback de baixo para cima
Os últimos frames costumam mostrarmodule_ximportandomodule_yenquantomodule_yjá está na pilha. -
Desenhe um esboço de dependência em uma direção
Liste cada módulo e o que ele importa. Qualquer seta que “suba” de modelos/core para app/UI/API é suspeita. -
Classifique a necessidade
- Valor/função em runtime → reestruture ou lazy import
- Só tipo →
TYPE_CHECKING - Constantes/modelos compartilhados → extraia para um módulo folha
-
Aplique a menor correção correta
Prefira extrair módulo compartilhado > lazy import > redesenhar o layout do pacote. Evite o mito de que “import absoluto resolve”. -
Rode de novo pelo mesmo entrypoint
Confirme tanto o estilopython a.pyquanto o-mde pacote, se você usa os dois.
Correção 1: Extrair um terceiro módulo (melhor padrão)
Quando models e services precisam de User, não faça um importar o outro. Coloque User em um módulo folha que não depende das camadas de cima.
app/
models/user.py # leaf: no import from services
services/billing.py # imports models.user
api/routes.py # imports services# models/user.py
class User:
def __init__(self, user_id: int, email: str):
self.user_id = user_id
self.email = email
@classmethod
def get(cls, user_id: int) -> "User":
return cls(user_id, f"user{user_id}@example.com")# services/billing.py
from models.user import User
def invoice(user_id: int) -> str:
user = User.get(user_id)
return f"Invoice for {user.email}"# api/routes.py
from services.billing import invoice
def handle(user_id: int) -> str:
return invoice(user_id)A direção da dependência é única: api → services → models. O ciclo some porque nada de baixo importa nada de cima.
Correção 2: Lazy import (import local)
Importe dentro da função ou método que precisa do símbolo quando um ciclo temporário é difícil de desatar.
# reporters.py
def build_report(order_id: str) -> dict:
from orders import Order # imported at call time, not module load time
order = Order.load(order_id)
return {"order_id": order.id, "total": order.total}# orders.py
class Order:
def __init__(self, id: str, total: float):
self.id = id
self.total = total
@classmethod
def load(cls, order_id: str) -> "Order":
return cls(order_id, 19.99)
def pretty(self) -> str:
from reporters import build_report # only if you truly need this edge
return str(build_report(self.id))Quando lazy import faz sentido
- Quebrar um ciclo teimoso rápido em código legado
- Dependências opcionais e pesadas (importar só no caminho que precisa)
Quando evitar como desenho de longo prazo
- Caminhos quentes em que o custo de import importa (em geral pequeno, mas mensurável)
- Arquitetura que continua gerando ciclos novos; nesse caso, extraia módulos
Correção 3: TYPE_CHECKING para imports só de anotação
Se o único motivo do import é type hint, deixe-o fora do runtime:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from models.user import User # not imported at runtime
def notify(user: User, message: str) -> None:
print(user.email, message)Com from __future__ import annotations (ou anotações entre aspas "User"), o Python não precisa de User em runtime para a anotação funcionar. Essa é a ferramenta certa para ciclos de type hints, não para chamar métodos em User.
Correção 4: Injetar dependências em vez de importá-las
Para serviços que “precisam um do outro”, passe colaboradores por parâmetro em vez de importar globalmente.
# notifications.py
class Notifier:
def send(self, email: str, body: str) -> None:
print(f"to={email} body={body}")
# checkout.py
class Checkout:
def __init__(self, notifier: "Notifier"):
self.notifier = notifier
def complete(self, email: str) -> None:
# business logic...
self.notifier.send(email, "Order complete")# main.py
from notifications import Notifier
from checkout import Checkout
checkout = Checkout(Notifier())
checkout.complete("a@example.com")Nem notifications nem checkout importam o outro. A composição na borda (main.py) cuida da fiação. É a mesma ideia de injeção por construtor em apps e frameworks maiores.
Armadilhas comuns (conselhos que não quebram o ciclo)
Esses aparecem em guias antigos. Resolvem outros problemas, não importação circular.
| Conselho que você pode ver | Realidade |
|---|---|
| “Use import absoluto” | Import absoluto melhora clareza. Não remove dependência mútua. |
“Defina __all__” | Só controla a superfície de from module import *. Zero efeito no ciclo. |
“Sempre use importlib.import_module” | Import dinâmico pode adiar o carregamento, mas se você ainda importar na hora errada, o ciclo permanece. Prefira import local explícito ou reestruturação. |
| “O Python entra em loop infinito” | O resultado usual é ImportError / inicialização parcial, não um loop rodando para sempre. |
| “Renomeie o arquivo e torça” | Conflito de nome pode gerar erro confuso de import, mas renomear sozinho não corrige uma dependência real A↔B. |
Padrões de layout de pacote que evitam ciclos
Pacotes saudáveis parecem um DAG (grafo acíclico dirigido):
package/
__init__.py # keep thin; avoid importing everything eagerly
domain/ # pure models, no IO
services/ # use domain
adapters/ # DB, HTTP, filesystem
api/ # entrypoints; imports services onlyRegras práticas:
- Módulos folha não importam nada do app.
__init__.pydeve ficar enxuto. Cadeias ansiosas defrom .a import */from .b import *são fábrica clássica de ciclo.- Prefira imports explícitos de módulos folha estáveis em vez de reexportações “de conveniência” que puxam metade do pacote.
- Rode como pacote ao desenvolver bibliotecas:
python -m package.apievita gambiarras de path que mascaram dependências reais. Veja também como executar scripts Python.
Se você mexe muito em arquivos ao refatorar, pathlib deixa scripts de mover/renomear mais claros do que cola de path em string.
Kit de depuração
Rastrear onde o ciclo entra
# debug_import.py
import sys
import trace
tracer = trace.Trace(count=False, trace=True)
tracer.runfunc(lambda: __import__("your_package.entry"))No dia a dia, o traceback basta. Em codebases grandes, ferramentas ajudam:
python -X importtime -c "import your_package"— mostra ordem e custo de import- import-linter (terceiros) — codifica “services não podem importar api” como regra de CI
- pyright / mypy — pegam erros de
TYPE_CHECKINGcedo quando você usa anotações
Capturar sintomas de inicialização parcial em testes
def test_package_imports_cleanly():
import importlib
import your_package.api as api
importlib.reload(api) # optional stress
assert hasattr(api, "handle")Se os testes unitários só importam módulos minúsculos isolados, eles podem não ver ciclos que só aparecem pelo entrypoint real do app. Adicione um smoke test que importa o módulo de entrada de verdade.
Tabela de decisão: qual correção usar?
| Sinal | Prefira |
|---|---|
| Modelo/constante compartilhado pelos dois lados | Extrair terceiro módulo |
| Só um caminho de chamada precisa do outro módulo | Lazy import nessa função |
| Import existe só para o type checker | TYPE_CHECKING |
| Dois serviços se orquestram mutuamente | Injeção de dependência / callbacks / eventos |
| Ciclos voltam depois de remendos | Regras de camadas do pacote + lint no CI |
FAQ
Conclusão
Importação circular é um problema de grafo de dependências. O Python mostra isso como erro de inicialização parcial — em especial a mensagem “most likely due to a circular import”. Corrija tornando as dependências unidirecionais: extraia folhas compartilhadas, use lazy import só onde for necessário, mantenha imports de anotação atrás de TYPE_CHECKING e ligue colaboradores de fora quando dois serviços se precisam.
Ignore “folclore” de correção — import absoluto, __all__ e renomear arquivo não dissolvem um ciclo real. Com o grafo limpo, o import volta a ser chato — e é exatamente isso que você quer.
Guias relacionados
- Python type hints — padrões de anotação que combinam com
TYPE_CHECKING - Python pathlib — paths mais limpos enquanto você reorganiza pacotes
- Como executar scripts Python — entrypoints (
python,-m) que afetam o comportamento de import - Python try/except — tratar e diagnosticar falhas de import em runtime
- Python dataclasses — modelos de domínio leves que cabem em módulos folha
- Python decorators — padrões que às vezes introduzem efeitos colaterais na hora do import