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.

Python Avançado: Performance Explicada Simplesmente (com Diagramas e Código Real)

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.

tl;dr
  • Introdução e Instalação
  • Perfilagem do código
  • Comprehensions iterators generators
  • Multithreading vs Multiprocessing
  • asyncio e coroutines
~$ cat ./parcours.md # Python Avancé Performance — 10 capítulos
01
Introdução e Instalação
→ Apresentação do curso→ Instalar as ferramentas de perfilamento+ 1 mais lições
02
Perfilamento do código
→ cProfile et pstats→ timeit para medições precisas+ 1 mais lições
03
Compreensões, iteradores e geradores
→ Compreensões de lista, conjunto e dicionário→ O protocolo iterador+ 1 mais lições
04
Multithreading vs Multiprocessing
→ Threading, o GIL e seus limites→ Multiprocessing, o verdadeiro paralelismo+ 1 mais lições
05
asyncio e coroutines
→ Introdução ao asyncio→ async / await na prática+ 1 mais lições
06
Cython Numba e vetorização
→ Vetorização com NumPy→ Numba JIT+ 1 mais lições
07
Gerenciamento de memória
→ O garbage collector Python→ Referências fracas com weakref+ 1 mais lições
08
Caching e memoização
→ functools.lru_cache→ Cache em disco com joblib e diskcache+ 1 mais lições
🏁
Projeto final (+ 2 capítulos no caminho)
→ Você sai com um projeto concreto e demonstrável

Apresentação do curso

NOTEObjetivo — Compreender o que significa « otimizar código Python », quando fazer (e principalmente quando não fazer), e ter uma visão clara do percurso que o espera neste curso.

Objetivos pedagógicos

TIPAo final deste módulo — Você saberá explicar por que Python é considerado lento (e por que isso é em parte um mito), conhecerá os 3 grandes eixos de otimização e entenderá a regra de ouro: medir antes de otimizar.

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

NOTEPython é realmente lento? — Python puro (CPython) pode ser 50 a 100× mais lento que C para loops numéricos. Mas, a maioria das bibliotecas científicas (NumPy, Pandas, scikit-learn) é escrita em C ou Fortran. Bem utilizado, Python atinge 80 a 95 % do desempenho do C. O problema quase nunca é « Python é lento », mas « meu código Python está mal escrito ».

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)
WARNINGOrdem de ataque — Sempre nesta ordem: algoritmo > estrutura > paralelismo. Paralelizar um algoritmo ruim é colocar 8 pessoas para cavar um túnel com colher de chá quando uma escavadeira bastava.

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.

TIPConsequência — Não é preciso otimizar todo seu código. Encontre as 5 linhas que custam 95 % do tempo, otimize-as agressivamente e deixe o resto em paz.

Donald Knuth, lenda da computação, resumiu assim em 1974:

NOTE« Premature optimization is the root of all evil. »
(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.

output
# 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

NOTEObjetivo — Descobrir 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

TIPAo final deste módulo — Você saberá escrever um gerador, usá-lo em um pipeline de processamento e escolher inteligentemente entre lista, gerador e coleção materializada.

Um primeiro gerador

output
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
NOTEMagia — Assim que uma função contém um 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

returnyield
Termina a funçãoSuspende a função
O estado é perdidoO estado é conservado
Retorna um valor (uma vez)Pode ser chamado várias vezes
Retorna tudo de uma vezRetorna um elemento por vez

Caso de uso real: ler um arquivo de log grande

output
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), ...]
TIPA arte do pipeline — Cada gerador faz uma única coisa e passa o resultado ao próximo. É o modelo das ferramentas Unix: cat file | grep ERROR | awk '{print $NF}' | sort | uniq -c. Legível, modular, memória constante.

yield from: delegar a outro gerador

output
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).

output
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

output
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:

output
data = [i*i for i in range(5)]   # lista, reutilizável

Perfilagem e encontrar os bottlenecks

NOTEObjetivo — Aplicar o método do capítulo 1 ao nosso pipeline ETL: usar cProfile para ver onde o tempo foi gasto, snakeviz para visualizar e line_profiler para ampliar na função crítica.

Objetivos pedagógicos

TIPAo final deste módulo — Você saberá perfilar um script de produção completo, ler o relatório, identificar as 3-4 linhas que consomem 90 % do tempo e escrever um « relatório de diagnóstico » claro para sua equipe.

1. cProfile global

output
python -m cProfile -o pipeline.prof pipeline_v0.py

Para explorar interativamente com pstats:

output
python -m pstats pipeline.prof
% sort cumulative
% stats 15
output
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)
NOTELeituratraiter_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

output
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:

output
@profile
def traiter_transactions(produits):
    ...
output
kernprof -l -v pipeline_v0.py
va-plus-loin

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 Code

FAQ

Quanto tempo para aprender Python Avançado Performance?
Com uma progressão estruturada (11 capítulos, 35 lições curtas e práticas), você atinge um nível operacional em algumas semanas, dedicando 30 a 60 minutos por dia. O importante é praticar cada conceito imediatamente.
É preciso ter pré-requisitos?
É melhor estar à vontade com os fundamentos da área: este conteúdo vai em profundidade, com casos reais.
Por onde começar concretamente?
Reproduza os comandos deste artigo e depois siga o curso completo Python Avançado Performance: ele encadeia as 35 lições em ordem, com exercícios e projeto final.

📬 Quer receber este tipo de guia toda semana? Inscreva-se gratuitamente — código real, zero enrolação.