Python Avançado: Performance Explicada Simplesmente (com Diagramas e Código Real)
Python Avançado Performance: o essencial em um artigo — código real, diagramas e etapas concretas, extratos de um curso de 35 lições.
Um guia direto ao ponto: Python Avançado Performance dissecado com diagramas, exemplos concretos e comandos testados. Tudo vem de um curso estruturado de 11 capítulos — aqui está o melhor.
- Introdução e Instalação
- Perfilagem do código
- Comprehensions iterators generators
- Multithreading vs Multiprocessing
- asyncio e coroutines
Apresentação do curso
Objetivos pedagógicos
O problema concreto
Você já viveu esta cena? Você executa seu script Python às 17h para calcular um relatório, vai buscar um café e, quando volta 30 minutos depois, o script ainda está rodando sempre. Pior: seu colega que faz a mesma coisa em R ou Julia terminou em 3 minutos.
Sintomas típicos de um programa Python lento
O que você vai aprender a fazer
Os 3 grandes eixos de otimização
| Eixo | Pergunta feita | Ganho típico |
|---|---|---|
| 1. Algoritmo | Meu código está em O(n²) quando poderia estar em O(n log n)? | 10× a 10 000× |
| 2. Estrutura de dados | Estou usando uma list quando um set resolveria? |
10× a 1000× |
| 3. Concorrência / paralelismo | Posso executar essas 8 tarefas ao mesmo tempo? | 2× a 16× (conforme CPU/IO) |
A regra dos 80/20 (Pareto)
Em 80 % dos programas, 80 % do tempo de execução é gasto em 20 % do código. Frequentemente é até 90/10 ou 95/5.
Donald Knuth, lenda da computação, resumiu assim em 1974:
(A otimização prematura é a raiz de todos os males.)
Tradução prática: escreva primeiro código claro e correto. Aproveite. Se estiver muito lento, otimize apenas as partes quentes. Caso contrário, você perde tempo tornando ilegível um código que não impactava ninguém.
Um antes/depois que fala
Aqui está um exemplo real: somar os quadrados dos números de 0 a 10 milhões.
# Versão ingênua: loop Python total = 0 for i in range(10_000_000): total += i * i # Tempo: ~1.2 segundo em um laptop moderno # Versão vetorizada com NumPy import numpy as np arr = np.arange(10_000_000) total = (arr * arr).sum() # Tempo: ~0.04 segundo -> 30× mais rápido # Versão compilada com Numba @jit from numba import jit @jit(nopython=True) def somme_carres(n): total = 0 for i in range(n): total += i * i return total # Tempo: ~0.01 segundo -> 120× mais rápido
Mesmo problema, mesma linguagem, mesmo resultado matemático — mas 120 vezes mais rápido. É exatamente isso que você vai aprender a fazer, sistematicamente, no seu próprio código.
O que você vai construir
Fase 1: Medir (ch. 0-1)
Instalar as ferramentas, fazer seu primeiro perfil, saber ler um relatório cProfile. Você saberá identificar o gargalo em menos de 5 minutos.
Fase 2: Otimizar (ch. 2-7)
Geradores, threading, multiprocessing, asyncio, NumPy, Numba, Cython, cache. Todo o arsenal moderno do desenvolvedor Python.
Geradores com yield
yield, a palavra-chave que transforma uma função em gerador, e aprender a construir pipelines de processamento de dados preguiçosos capazes de tratar arquivos de vários gigabytes com poucos megabytes de RAM.Objetivos pedagógicos
Um primeiro gerador
def compter(max): n = 0 while n < max: yield n # suspende a função e retorna n n += 1 # Chamada: não faz NADA, recuperamos um gerador g = compter(5) print(type(g)) # <class 'generator'> # Consumo for n in g: print(n) # 0 1 2 3 4
yield, Python a transforma em uma fábrica de geradores. A chamada não dispara o código: retorna um objeto gerador. O código só é executado a cada chamada de next().yield vs return
| return | yield |
|---|---|
| Termina a função | Suspende a função |
| O estado é perdido | O estado é conservado |
| Retorna um valor (uma vez) | Pode ser chamado várias vezes |
| Retorna tudo de uma vez | Retorna um elemento por vez |
Caso de uso real: ler um arquivo de log grande
def lire_log(chemin): """Gerador que yield cada linha, sem carregar tudo.""" with open(chemin, encoding="utf-8") as f: for ligne in f: yield ligne.rstrip("\n") def filtrer_erreurs(lignes): """Gerador que mantém apenas as linhas ERROR.""" for ligne in lignes: if "ERROR" in ligne: yield ligne def extraire_codes(lignes): """Gerador que yield o código HTTP de cada linha.""" for ligne in lignes: try: code = int(ligne.split()[-1]) yield code except (ValueError, IndexError): continue # Pipeline: nenhuma das etapas consome RAM, mesmo para 50 GB lignes = lire_log("acces.log") erreurs = filtrer_erreurs(lignes) codes = extraire_codes(erreurs) # Consumo final from collections import Counter print(Counter(codes).most_common(5)) # [(500, 1284), (502, 412), (503, 309), ...]
cat file | grep ERROR | awk '{print $NF}' | sort | uniq -c. Legível, modular, memória constante.yield from: delegar a outro gerador
def sous_compter(a, b): for i in range(a, b): yield i def compter_tout(): yield from sous_compter(0, 3) # 0,1,2 yield from sous_compter(10, 13) # 10,11,12 yield 99 print(list(compter_tout())) # [0, 1, 2, 10, 11, 12, 99]
yield from evita o loop for x in autre: yield x e também trata corretamente exceções e valores enviados.
send() : geradores bidirecionais
É possível enviar valores a um gerador (pouco usado, mas poderoso).
def echo(): while True: recu = yield print("Recebido :", recu) g = echo() next(g) # iniciar o gerador g.send("hello") g.send("world") # Exibe: Recebido : hello / Recebido : world
Esse mecanismo deu origem ao asyncio antes do Python 3.5. Hoje prefere-se async/await.
Armadilha n°1: um gerador só pode ser percorrido uma vez
g = (i*i for i in range(5)) print(list(g)) # [0, 1, 4, 9, 16] print(list(g)) # [] -- ATENÇÃO, g está esgotado
Solução: recriar o gerador ou materializar em lista se precisar usar várias vezes:
data = [i*i for i in range(5)] # lista, reutilizável
Perfilagem e encontrar os bottlenecks
Objetivos pedagógicos
1. cProfile global
python -m cProfile -o pipeline.prof pipeline_v0.py
Para explorar interativamente com pstats:
python -m pstats pipeline.prof % sort cumulative % stats 15
42_847_310 function calls in 1083.42 seconds
ncalls tottime cumtime filename:lineno(function)
1 0.000 1083.42 pipeline_v0.py:1(<module>)
1 0.005 1083.41 pipeline_v0.py:42(main)
1 654.21 750.18 pipeline_v0.py:11(traiter_transactions)
5000001 34.20 34.20 <built-in method strip>
5000001 28.45 28.45 <built-in method upper>
3750000 21.89 45.30 pipeline_v0.py:18(traiter_transactions/dict.get)
5000001 18.95 18.95 <built-in method float>
3750000 180.45 180.45 list.append (resultats)
1 268.32 268.32 pipeline_v0.py:32(agreger)
1 65.10 65.10 pipeline_v0.py:39(sauver)traiter_transactions = 70 % do tempo. Dentro: strip/upper (60 s), append (180 s), float() (19 s). agreger = 25 %. sauver = 6 %. Portanto a prioridade #1 = tratar_transactions.2. Visualizar com snakeviz
snakeviz pipeline.prof
Um navegador abre. Vista « sunburst »: um círculo central que representa o programa inteiro, dividido em setores proporcionais ao tempo. Clique para ampliar.
No nosso perfil, vemos imediatamente:
3. Ampliar com line_profiler
Decoramos a função crítica:
@profile
def traiter_transactions(produits):
...kernprof -l -v pipeline_v0.py
Este artigo cobre os trechos mais úteis — o curso completo Python Avançado Performance (11 capítulos, 35 lições, exercícios corrigidos e projeto final) leva você até o fim.
./acceder-au-cours-complet curso gratuito : Dominar Claude CodeFAQ
Quanto tempo para aprender Python Avançado Performance?
É preciso ter pré-requisitos?
Por onde começar concretamente?
📬 Quer receber este tipo de guia toda semana? Inscreva-se gratuitamente — código real, zero enrolação.