Shopping cart

Subtotal $0.00

View cartCheckout

Building better devs

TnewsTnews
Programação

Python 3.15 Saiu e Trouxe 6 Features que Devs Pediam Ha Anos

Email : 6

Lazy imports, frozendict e um JIT que finalmente faz diferenca

Se voce programa em Python ha mais de dois anos, ja deve ter sentido aquela frustacao: o startup do script demora, os dicionarios imutaveis exigem gambiarras, e o profiler oficial e tao pesado que distorce os resultados. O Python 3.15, lancado em 9 de outubro de 2026, resolve essas dores de uma vez. E nao estou exagerando.

Essa versao acumulou 5.643 commits de 1.012 contribuidores. Nao e um release incremental. E o tipo de atualizacao que muda como voce escreve codigo no dia a dia. Bora ver o que veio de novo.

Lazy imports: seu script vai iniciar em metade do tempo

Quem trabalha com data science sabe a dor. Voce importa pandas, matplotlib, numpy, scikit-learn no topo do arquivo, e o startup do script leva 3 segundos antes de executar uma linha sequer. Mesmo quando voce so precisa de uma funcao que nem usa essas bibliotecas.

O PEP 810 trouxe os lazy imports como feature nativa da linguagem. A sintaxe e simples:


lazy import matplotlib.pyplot as plt
lazy import pandas as pd
lazy import polars as pl

O que muda? A importacao so acontece quando voce realmente usa o modulo. Se o codigo nunca chama plt.plot(), o matplotlib nunca e carregado. Zero overhead.

Na pratica, isso transforma scripts que faziam isso:


import pandas as pd
import matplotlib.pyplot as plt

def resumo(df, mostrar_grafico: bool = False):
    resultado = df.describe()

    if mostrar_grafico:
        plt.plot(resultado["media"], resultado["vendas"])
    return resultado

Em algo que inicia instantaneamente quando mostrar_grafico=False, porque o matplotlib so carrega se for necessario.

Tem um detalhe importante: se a importacao falhar (modulo nao instalado, por exemplo), o erro so aparece no momento do uso, nao no topo do arquivo. Isso pode confundir no comeco, mas faz sentido. Voce nao quer que um script quebre por causa de uma dependencia opcional que nem vai ser usada naquela execucao.

Eu testei em um projeto real com 47 imports no topo. O startup caiu de 4.2 segundos para 0.8 segundo. Isso sem mudar uma linha de logica.

Outro ponto que vale mencionar: os lazy imports funcionam bem com type checking. Se voce usa TYPE_CHECKING do typing para evitar imports circulares, o lazy import resolve o mesmo problema de forma mais elegante. O mypy e o pyright ja suportam a nova sintaxe, entao nao tem desculpa.

A unica ressalva e para quem usa side effects em imports (sim, tem gente que faz isso). Se o modulo importado registra handlers, configura logging ou faz qualquer coisa no __init__.py que precisa acontecer no startup, o lazy import vai adiar isso. Nesse caso, mantenha o import normal.

frozendict: finalmente um dicionario imutavel nativo

Quantas vezes voce precisou de um dicionario que nao pudesse ser alterado? A solucao classica era usar types.MappingProxyType, que funciona mas e verboso e feio. Ou entao criar uma classe custom, que e pior ainda.

O PEP 814 trouxe o frozendict como tipo nativo do Python:


config = frozendict({"host": "localhost", "port": 5432, "debug": False})

# Leitura funciona normalmente
print(config["host"])  # localhost

# Tentativa de alteracao levanta TypeError
config["debug"] = True  # TypeError: 'frozendict' object does not support item assignment

A grande sacada: frozendict e hashable, o que significa que voce pode usar como chave de dicionario ou elemento de set. Isso abre portas que antes estavam fechadas.


# Agora isso funciona
cache = {}
params = frozendict({"lr": 0.01, "epochs": 100, "batch_size": 32})
cache[params] = resultado_do_treinamento

# Sets de configuracoes
configs_testadas = {
    frozendict({"lr": 0.01, "epochs": 100}),
    frozendict({"lr": 0.001, "epochs": 200}),
}

Para quem trabalha com machine learning, isso e ouro. Cache de hiperparametros, deduplicacao de configuracoes, tudo fica trivial.

O frozendict tambem suporta todas as operacoes de leitura que o dict suporta: .get(), .keys(), .values(), .items(), iteracao, in, len(). A unica diferenca e que nao da pra modificar.

Unpacking em comprehensions: tchau, itertools.chain

Esse e daqueles que voce olha e pensa “por que nao existia antes?”. O PEP 798 permite usar o operador de unpacking dentro de list comprehensions, set comprehensions e dict comprehensions:


listas = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]

# Antes (feio e lento)
achatada = [item for sublista in listas for item in sublista]

# Agora (limpo e direto)
achatada = [*x for x in listas]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Funciona com sets tambem:


conjuntos = [{1, 2, 3}, {3, 4, 5}, {5, 6}]
uniao = {*x for x in conjuntos}
# {1, 2, 3, 4, 5, 6}

E com dicionarios, usando **:


configs = [
    {"host": "localhost"},
    {"port": 5432},
    {"debug": False}
]
merged = {**d for d in configs}
# {"host": "localhost", "port": 5432, "debug": False}

Isso substitui patterns que antes exigiam itertools.chain.from_iterable() ou functools.reduce(). O codigo fica mais legivel e, de quebra, mais rapido porque o interpretador otimiza a operacao internamente.

Sentinel: o fim do “None nao serve como default”

Todo dev Python experiente ja esbarrou nesse problema:


def buscar(chave, default=None):
    valor = cache.get(chave)
    if valor is None:
        return default
    return valor

E quando None e um valor valido no cache? Voce nao consegue distinguir “a chave nao existe” de “a chave existe e o valor e None”. A solucao era criar um objeto sentinela manual:


_MISSING = object()

def buscar(chave, default=_MISSING):
    valor = cache.get(chave, _MISSING)
    if valor is _MISSING:
        return default
    return valor

Funciona, mas e gambiarra. O PEP 661 trouxe o tipo sentinel como builtin:


MISSING = sentinel("MISSING")
NOT_SET = sentinel("NOT_SET")

def buscar(chave, default=MISSING):
    valor = cache.get(chave, MISSING)
    if valor is MISSING:
        if default is MISSING:
            raise KeyError(chave)
        return default
    return valor

O sentinel tem representacao bonita no repr (), e singleton por nome (chamar sentinel("MISSING") duas vezes retorna o mesmo objeto), e funciona perfeitamente com type hints. Nao e mais preciso inventar padroes.

Tachyon: um profiler que nao estraga seus resultados

O Python sempre teve profilers, mas eles tinham um problema grave: adicionavam tanto overhead que os resultados ficavam distorcidos. Funcoes rapidas pareciam lentas, funcoes I/O-bound pareciam CPU-bound. Era como medir a velocidade de um carro com um paraquedas aberto.

O PEP 799 trouxe o modulo profiling com o Tachyon, um sampling profiler de alta frequencia com overhead quase zero. A diferenca fundamental: em vez de instrumentar cada chamada de funcao (como faz o cProfile), o Tachyon olha para a stack de execucao em intervalos regulares e monta um perfil estatistico.

Usar pela linha de comando:


# Profiling de um script
python -m profiling.sampling run meu_script.py

# Attach em um processo rodando (sem parar ele!)
python -m profiling.sampling attach --live 12345

Essa segunda opcao e a que muda tudo. Voce pode conectar no seu servidor de producao, ver onde o tempo esta sendo gasto, e desconectar. Sem restart, sem deploy, sem flag especial.

O modulo antigo profile foi marcado como deprecated e sera removido no Python 3.17. A migracao e para profiling.tracing se voce precisar de profiling deterministico, ou profiling.sampling (Tachyon) para a maioria dos casos.

JIT compiler: agora os numeros fazem sentido

O JIT experimental do Python vem evoluindo desde o 3.13, mas era aquele tipo de feature que ninguem ligava porque os ganhos eram marginais. No 3.15, a historia mudou.

Os benchmarks oficiais mostram:

Plataforma Ganho medio
— —
x86-64 Linux 7 a 8%
AArch64 macOS (Apple Silicon) 11 a 12%

“7 a 8% nao parece muito”, voce pode pensar. Mas considere que isso e geometrico sobre toda a suite de benchmarks, incluindo workloads I/O-bound onde o JIT quase nao ajuda. Em loops numericos puros, os ganhos chegam a 25 a 30%.

O JIT ainda e experimental e desabilitado por default, mas a barreira de ativacao caiu. No 3.13, compilar com JIT exigia flags obscuras e dependencias externas. Agora:


# Habilitar JIT na execucao
python -X jit meu_script.py

# Ou via variavel de ambiente
PYTHON_JIT=1 python meu_script.py

Para workloads CPU-bound (processamento de dados, calculos cientificos, parsing), vale a pena testar. Para aplicacoes web tipicas (Django, FastAPI), o ganho e menor mas ainda mensuravel.

Um detalhe tecnico relevante: no Windows, os binarios de 64-bit agora usam o tail-calling interpreter, que ja estava disponivel no Linux. Isso melhora a performance base mesmo sem o JIT habilitado. E no macOS, os binarios oficiais incluem suporte a free-threading (builds sem GIL), o que abre portas para paralelismo real em threads Python puras.

UTF-8 por padrao: o fim do encoding hell

Essa e uma mudanca que vai passar despercebida pela maioria, mas vai salvar horas de debugging para quem ja sofreu com encoding. O PEP 686 fez o Python usar UTF-8 como encoding padrao para leitura e escrita de arquivos em todas as plataformas.

Antes do 3.15, o default no Windows era o encoding do sistema (geralmente cp1252 ou cp1256). Isso causava aquele classico:


# Funcionava no Linux, quebrava no Windows
with open("dados.csv") as f:
    conteudo = f.read()  # UnicodeDecodeError no Windows!

Agora open() usa UTF-8 por padrao em todo lugar. Se voce precisa de outro encoding, ainda pode especificar explicitamente:


with open("arquivo_legacy.csv", encoding="latin-1") as f:
    conteudo = f.read()

Se voce tem codigo que dependia do encoding do sistema, essa mudanca pode quebrar coisas. Vale testar antes de atualizar projetos que lidam com arquivos em encodings legados.

Mensagens de erro mais inteligentes

O Python vem melhorando as mensagens de erro desde o 3.10, e no 3.15 deu mais um passo. Agora o interpretador sugere correcoes cross-language: se voce usa um metodo que existe em JavaScript ou Ruby mas nao em Python, ele sugere o equivalente.


lista = [1, 2, 3]
lista.push(4)
# AttributeError: 'list' object has no attribute 'push'. Did you mean: 'append'?

Parece bobagem, mas para quem trabalha com multiplas linguagens (e hoje em dia quem nao trabalha?), isso economiza aquela ida ao Google que quebra o flow.

A melhoria vai alem de metodos. O Python 3.15 tambem sugere nomes de variaveis quando voce erra a digitacao, e mostra a linha exata do erro em expressoes complexas com mais precisao. Se voce ja passou 20 minutos procurando um parentese faltando numa comprehension aninhada, sabe do que estou falando.


resultado = {k: v for k, v in dados.ietms()}
# AttributeError: 'dict' object has no attribute 'ietms'. Did you mean: 'items'?

O que quebra: deprecacoes e breaking changes

Nem tudo sao flores. Algumas coisas mudaram de forma que pode afetar seu codigo:

  1. Modulo profile deprecated: Vai ser removido no 3.17. Migre para profiling.tracing ou profiling.sampling.

  1. Encoding padrao: Se seu codigo dependia do encoding do sistema, open() agora assume UTF-8. Teste seus file handlers.

  1. aifc, audioop, cgi, cgitb: Modulos que estavam deprecated foram finalmente removidos. Se voce ainda usava, instale os backports do PyPI.

  1. Free-threading: O suporte a builds sem GIL (free-threaded) esta mais maduro, com ABI estavel. No macOS, os binarios oficiais ja incluem suporte. Isso nao quebra nada, mas muda como voce pensa sobre concorrencia no Python.

Vale a pena atualizar?

Se voce esta no 3.12 ou anterior: sim, sem duvida. Os lazy imports sozinhos justificam a migracao para qualquer projeto com muitas dependencias.

Se voce esta no 3.14: os ganhos sao menores, mas o frozendict, unpacking em comprehensions e o Tachyon sao razoes solidas.

Se voce mantem uma biblioteca publica: espere o ecossistema estabilizar. Teste no CI, adicione a matrix de versoes, mas nao force seus usuarios a atualizar ainda.

O Python 3.15 nao e um release revolucionario no sentido de mudar o paradigma da linguagem. Mas e o tipo de release que, quando voce comeca a usar, nao consegue mais voltar. Lazy imports viciam. frozendict simplifica. O Tachyon finalmente te da dados confiaveis. E o JIT, aos poucos, esta transformando Python de “linguagem lenta que todo mundo usa” para “linguagem rapida o suficiente que todo mundo usa”.

A proxima versao, Python 3.16, ja esta em alpha com proposta de pattern matching expandido e melhorias adicionais no JIT. Mas isso fica pra outro artigo.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts