Cenários de Importação Circular
Importações circulares acontecem quando o módulo A carrega o módulo B enquanto B ainda está inicializando e tenta importar A. Python levanta um ImportError ou deixa nomes como placeholders meio inicializados, semelhantes a None.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
# Diagnosticar
python -c "import meu_pacote"
python -X importtime -c "import meu_pacote" 2>&1 | head
# Correção estrutural: extrair tipos compartilhados
# meu_pacote/dominio/tipos.py <- ambos os lados importam este módulo folhaQuando usar isto:
ImportError: cannot import name 'X' from partially initialized module- Tipos funcionam em tempo de execução, mas mypy/pyright relatam importações circulares
- Adição de novo módulo quebra a ordem de inicialização que "costumava funcionar"
- Importações preguiçosas se multiplicam como um mecanismo de enfrentamento
Exemplo de Trabalho
# Layout quebrado:
# orders/models.py importa billing/invoices.py
# billing/invoices.py importa orders/models.py
# Passo 1: reproduzir
# $ python -c "import orders.models"
# ImportError: cannot import name 'Order' from partially initialized module 'orders.models'
# Passo 2: extrair tipos folha
# shared/types.py
from dataclasses import dataclass
@dataclass(frozen=True)
class OrderId:
value: str
@dataclass(frozen=True)
class Money:
cents: int
# orders/models.py
from shared.types import Money, OrderId
@dataclass
class Order:
id: OrderId
total: Money
# billing/invoices.py
from shared.types import Money, OrderId
from orders.models import Order
def invoice_for(order: Order) -> Money:
return order.total# Ponte temporária (documentar ticket para remover)
def get_order(order_id: str):
from orders.models import Order # importação preguiçosa dentro da função
return Order(OrderId(order_id), Money(0))O que isto demonstra:
- Tipos de valor compartilhados movem-se para um módulo folha que ambos os lados importam
- Tipos de domínio importam para dentro; billing importa
Orderapenas apósorders.modelsterminar de carregar - Importação preguiçosa resolve emergências, mas não substitui a estrutura
Mergulho Profundo
Como Funciona
- A importação executa de cima para baixo uma vez por módulo; módulos incompletos ficam em
sys.modules - Acesso a atributos durante a inicialização parcial falha ou vê nomes ausentes
- Importações apenas para verificação de tipos (
if TYPE_CHECKING:) quebram ciclos para ferramentas estáticas sem custo de tempo de execução - Reexportações de
__init__.pyde pacotes podem acidentalmente criar ciclos entre subpacotes
Formas Comuns de Ciclo
| Padrão | Sintoma | Correção |
|---|---|---|
| Importações mútuas A ↔ B | ImportError na inicialização | módulo types folha |
models ↔ services | nome de classe ausente | camada de portas/casos de uso |
fábrica app ↔ blueprints | falha na inicialização Flask/Django | registro tardio de blueprints |
grafo de reexportação __init__ | importação lenta/pesada | API pública restrita, preguiçosa |
Notas de Python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from orders.models import Order
def describe(order: "Order") -> str:
return "order"Armadilhas
- Anotações apenas em string - as aspas escondem ciclos sem corrigir a arquitetura. Correção: extrair tipos ou inverter a dependência.
__init__.pygordo - importar subpacote executa toda a árvore. Correção:__init__.pyvazio ou mínimo; importações explícitas de submódulos.- Efeitos colaterais na importação - conexão com banco de dados na importação piora a depuração de ciclos. Correção: mover IO para
main/hooks de inicialização. - Coleta do pytest importa tudo - ciclos escondidos até que toda a suíte seja executada. Correção: teste de fumaça
import pacoteem CI. - Renomear sem inverter arestas - o ciclo retorna com novos nomes de módulo. Correção: regra de dependência: domínio nunca importa adaptadores.
- Importações circulares de
conftestde teste -conftest.pyimporta o app que importa os helpers de teste. Correção: fixtures emtests/supportdedicado sem importar módulos do app mutuamente.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Módulo folha types/protocols | formas de dados compartilhadas | comportamento ainda chama mutuamente |
| Mesclar módulos temporariamente | dois arquivos pequenos interligados | domínios grandes - cria módulo deus |
Importações TYPE_CHECKING | referências de volta apenas para tipagem | necessidades de tempo de execução de classe real |
| Importações de função preguiçosas | hotfix com ticket | design permanente |
FAQs
Como vejo a cadeia de importação?
Execute python -X importtime -c "import pkg" e leia as arestas mais lentas/cíclicas. Ferramentas como pydeps grafam importações estaticamente.
from __future__ import annotations corrige ciclos em tempo de execução?
Isso adia a avaliação de anotações (o comportamento de PEP 563 varia por versão), mas não corrige importações mútuas em tempo de execução de classes e funções.
Onde devem viver as portas de Protocol?
ports/ ou domain/protocols.py - importado por adaptadores e casos de uso, não por frameworks importando uns aos outros.
Importações circulares podem funcionar às vezes?
Acidentalmente, se um módulo só precisa do outro depois que ambos terminam a inicialização. Depende da ordem de importação e quebra quando refatorado.
Como os roteadores FastAPI criam ciclos?
Roteadores importam serviços que importam app para Depends. Correção: fábricas de dependência em um módulo wiring importado após a criação do app.
E sobre modelos Django importando uns aos outros?
Use referências de string 'other.Model' em ForeignKey e mova enums compartilhados para o módulo folha models/enums.py.
Devo usar importlib em código de produção?
Raramente. Importações dinâmicas obscurecem grafos. Prefira correções estruturais; use importlib apenas para plugins com pontos de entrada explícitos.
Como o layout src ajuda?
Força nomes de pacotes explícitos e captura importações relativas equivocadas que pioram ciclos entre caminhos de módulo duplicados.
O ruff pode detectar ciclos?
Regras de lint do ruff capturam alguns problemas de importação; contratos import-linter impõem a direção da camada em CI.
Quando mesclar módulos está correto?
Quando dois módulos têm <200 linhas, sempre mudam juntos e representam um único conceito delimitado. Documente a mesclagem em um ADR.
Como prevenir regressões?
Adicione python -c "import myservice" à CI e contratos opcionais import-linter entre domain, use_cases, adapters.
TYPE_CHECKING afeta o desempenho em tempo de execução?
Não. Importações sob if TYPE_CHECKING: são removidas em tempo de execução apenas por verificadores de tipo; elas não são executadas durante importações normais.
Relacionado
- Decisões de Refatoração - correções de ciclo classificadas
- Layout src e Estrutura de Pacotes - higiene de pacotes
- Arquitetura Limpa / Hexagonal - direção de dependência
- Ferramentas de Depuração - inspecionar estado de importação
- Conflitos de Dependência e Ambiente - pacote errado no caminho imita ciclos
Versões de Stack: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.