10 out 2026 · python, desempenho, desenvolvimento · 4 min
Dez imports custam 100 ms na partida do seu script Python
Um script que importa dez módulos da biblioteca padrão leva 102 ms para chegar à primeira linha útil. Com a mesma lista declarada como import preguiçoso, 14 ms. A diferença é a palavra lazy na frente de cada import.
Isso existe desde 9 de outubro, quando o Python 3.15 saiu com o PEP 810 implementado. A sintaxe é nova e explícita: você escreve lazy import asyncio e o módulo só é carregado quando alguém o usa pela primeira vez. O nome da operação na proposta é reificação, e o efeito prático é que a partida do programa deixa de pagar por código que talvez nunca rode.
O que medi
Compilei o 3.14.8 e o 3.15.0 com o mesmo compilador e as mesmas flags, e rodei dois scripts idênticos em conteúdo. Um importa dez módulos da biblioteca padrão no jeito antigo, o outro declara os mesmos dez com lazy import e não os usa. Sete execuções de cada, menor tempo:
| script | 3.14.8 | 3.15.0 |
|---|---|---|
| import comum, dez módulos | 103 ms | 100 ms |
| lazy import, mesmos dez módulos | erro de sintaxe | 14 ms |
O 3.14 nem aceita a sintaxe, o que é justo: import preguiçoso é recurso de 3.15. O número que interessa é o de baixo, 14 ms, e ele é sete vezes menor que a partida comum. A contagem de módulos carregados explica: o script com imports comuns terminou com 230 módulos em sys.modules, o preguiçoso terminou com 32.
Também medi o custo individual de cada import, num interpretador novo a cada linha:
| módulo | custo de importar |
|---|---|
| asyncio | 38,7 ms |
| urllib.request | 28,6 ms |
| unittest | 24,2 ms |
| logging.config | 22,2 ms |
| email.message | 14,3 ms |
| tarfile | 10,9 ms |
| inspect | 10,0 ms |
| statistics | 9,4 ms |
| ssl | 9,2 ms |
| json | 7,2 ms |
| argparse | 7,2 ms |
| re | 5,5 ms |
| sqlite3 | 3,9 ms |
Esses números não somam os 100 ms do script completo, porque vários módulos compartilham dependências e o custo aparece repetido em cada linha. A tabela serve para escolher candidato, não para somar orçamento.
Onde isso rende
CLI é o caso que salta. Ferramenta de linha de comando costuma ter um caminho rápido, do tipo --version ou --help, e um caminho pesado que faz o trabalho. O caminho rápido paga a importação de tudo e sai em 100 ms quando poderia sair em 15. Em servidor sem estado, com partida a frio por requisição, a mesma conta se repete multiplicada por carga.
# antes
import argparse, asyncio, sqlite3, unittest
# depois, em Python 3.15
lazy import argparse, asyncio, sqlite3, unittest
Para escolher o que deixar preguiçoso, meça antes de mexer:
python3.15 -X importtime seu_cli.py 2> tempos.txt
sort -k2 -nr tempos.txt | head -15
A saída lista cada import com o tempo acumulado. Os três primeiros da lista que o caminho rápido não usa são a resposta.
O que muda de comportamento
Import preguiçoso move o momento do erro, e isso tem consequência. Se o módulo está faltando, o ImportError aparece no primeiro uso, não na primeira linha do programa. Para quem escreve script de infraestrutura, isso muda o que o log mostra quando a dependência some da máquina.
Efeito colateral de import também atrasa. Módulo que configura log, registra plugin ou abre conexão ao ser importado passa a fazer isso no meio da execução, e o lugar onde isso acontece deixa de ser previsível. Módulo com efeito no import é candidato ruim para a lista.
A proposta trata os dois pontos com um mecanismo de filtro e um modo global, que aplicam a regra sem mudar o código. É mais poderoso e mais arriscado, porque passa a valer para biblioteca de terceiro que não foi escrita pensando nisso. Começar pelos imports que você mesmo controla é a versão chata que funciona.
Ressalva: a medição é de dois interpretadores compilados localmente, com a lista que eu escolhi, em sete execuções. O ganho depende do que está sendo importado e de quanto código do caminho rápido depende disso. import time não tem o que ganhar.
Minha opinião é que a mudança vale mais pelo diagnóstico que ela força do que pelo ganho em si. Todo CLI que eu escrevi carrega coisa que não usa, e nunca medi porque a partida parecia instantânea. Cem milissegundos por vez, multiplicados por todas as chamadas de um script de automação, viram alguns segundos de espera que ninguém atribuía à importação. É a mesma escolha de sempre, comprar tempo de partida com disciplina de código, e ela aparece em qualquer runtime quando você troca recurso por latência ou paga por pausas que ninguém vê.