Bolhaverso

Reader

pt-br

Leia os posts do bolha.blog.

en-us

Read all posts from our users.

from in.versos

Quero me fazer teu

Descobrir tua alma, deixá-la nua, quase transparente

Quero olhar em teus olhos e ver além, ver a profundidade do teu existir

Logo ao amanhecer quero mergulhar em teus mistérios, saber o que te tira do sério

Ouvir teus poemas — aqueles ocultos que ainda não recitou

Quero saber qual a tua expressão ao ver o sol nascer e se pôr

Quero tocar tua pele, sentir teu calor

Quero! Mas de querer não se vive a vida

 
Leia mais...

from Riverfount

Procurando por dicionários Python DSA, hash tables em Python, complexidade Big O dict Python ou estruturas de dados Python avançadas? Neste guia técnico desvendamos os princípios internos dos dicionários (dict), desde hashing e colisões até operações otimizadas para algoritmos reais. Ideal para engenheiros de software que buscam performance em microservices, grafos e entrevistas técnicas – leia e eleve seu código Python a outro nível!

Dicionários em Python (dict) são uma implementação eficiente de hash tables (tabelas de hash), uma estrutura de dados essencial em DSA para mapear chaves únicas a valores com acesso médio em tempo constante O(1). Essa performance os torna superiores a listas para operações de busca, inserção e deleção em cenários não ordenados, como caches, contagens de frequência ou representações de grafos. Desde Python 3.7, eles mantêm ordem de inserção, combinando benefícios de hash tables com listas ordenadas.[1]

Implementação Interna e Hashing

Internamente, o Python computa um hash da chave imutável (ex.: hash('chave')) para determinar o índice na tabela subjacente, um array redimensionável. Colisões são resolvidas por open addressing com probing quadrático. Chaves mutáveis (como listas) geram TypeError para evitar inconsistências.

Exemplo de hashing básico:

chave = 'abc'
hash_val = hash(chave)  # Resultado varia por sessão, ex: -123456789
print(f"Hash de '{chave}': {hash_val}")

Isso garante lookups rápidos, mas hashes ruins (ex.: ataques de hash-flooding) degradam para O(n) no pior caso.

Operações Fundamentais com Exemplos

Aqui estão as operações core, com análise de complexidade:

  • Criação e Inicialização:
  # Dict literal
  freq = {'a': 2, 'b': 1}
  
  # From iterable
  from collections import Counter
  freq = Counter('abacaxi')  # {'a': 3, 'b': 1, 'c': 1, 'x': 1, 'i': 1}
  • Inserção/Atualização (O(1) médio):

    freq['z'] = 1  # Insere ou atualiza
    freq.setdefault('y', 0)  # Insere só se ausente
    
  • Busca e Acesso (O(1) médio):

    valor = freq.get('a', 0)  # 3, com default se chave ausente
    if 'a' in freq:  # Verificação segura
      print(freq['a'])
    
  • Remoção (O(1) médio):

    del freq['z']  # Remove chave
    popped = freq.pop('b', None)  # Retorna valor ou default
    
  • Iteração Eficiente:

    # Chaves, valores ou itens
    for chave, valor in freq.items():
      print(f"{chave}: {valor}")
    

Complexidade Assintótica Detalhada

Operação Média (Amortizada) Pior Caso Notas
Inserção O(1) O(n) Redimensiona em load factor ~2/3
Busca (get) O(1) O(n) Colisões extremas
Deleção O(1) O(n) Marca como “tombstone”
Iteração O(n) O(n) Linear no tamanho
Len() O(1) O(1) Armazenado explicitamente

Boas Práticas e Casos de Uso em DSA

  • Evite chaves mutáveis: Use frozenset ou tuplas para chaves compostas.
  • Defaultdict para Simplicidade: python from collections import defaultdict graph = defaultdict(list) graph['A'].append('B') # Lista auto-criada
  • Aplicações:
    • Grafo de Adjacência: adj = {'A': ['B', 'C'], 'B': ['A']} para BFS/DFS.
    • Cache LRU Manual: Track acessos com dict + heapq.
    • Contagem de Frequência: Counter para anagramas ou sliding windows.
  • Alternativas: collections.OrderedDict para popitem(LRU), ou dict com __missing__ customizado.

Em projetos full-stack ou microservices, dicionários otimizam APIs REST (ex.: roteamento por ID) e automação, escalando para milhões de entradas sem gargalos.

Conclusão

Dominar dicionários é o primeiro passo para algoritmos escaláveis em Python – aplique esses conceitos hoje e veja seu código voar! Teste os exemplos no seu ambiente, experimente em LeetCode ou compartilhe em @riverfount@bolha.us como usou em projetos reais.

 
Read more...

from in.versos

Era doce como o mel Macio como pêssego

Belo como o céu Agradável como a brisa noturna

Quente como as chamas que fundem o aço A parte prazerosa da loucura

Mas tais prazeres são em sua essência, ardilosos

Sutil e suave veneno

Ontem era todos os versos de amor que um dia escrevi sem saber para quem

Hoje se desfez como a névoa de outono sob o acalento da lira de Apolo

 
Leia mais...

from in.versos

Ansiava estar ali, contigo — bailando sob a chuva

Me afogando em teus olhares enquanto me embriago em teus sorrisos

 
Leia mais...

from Riverfount

Generators em Python são funções especiais que usam yield para gerar valores sob demanda, economizando memória em vez de criar listas completas na RAM. Pense neles como “listas preguiçosas” que produzem um item por vez, ideais para processar arquivos grandes ou sequências infinitas sem travar o sistema.

Yield vs Return: A Diferença Fundamental

return encerra a função imediatamente após retornar um único valor, enquanto yield pausa a execução, retorna um valor e preserva o estado interno para continuar de onde parou na próxima chamada. Isso permite que uma única função gere múltiplos valores sequencialmente, como um loop “congelado” e retomado.

def com_return(n):
    for i in range(n):
        return i  # Para após o primeiro valor: sempre retorna 0

def com_yield(n):
    for i in range(n):
        yield i  # Gera 0, 1, 2... até n, pausando entre cada yield

print(next(com_return(5)))  # 0 (e função termina)
for i in com_yield(5):      # 0, 1, 2, 3, 4 (estado preservado)
    print(i)

return é para resultados finais únicos; yield constrói iteradores reutilizáveis.

Como Funcionam os Generators Básicos

Uma função generator parece normal, mas substitui return por yield, que pausa a execução e retorna um valor, preservando o estado para continuar depois. Isso cria um objeto iterável consumido com for ou next(), perfeito para loops sem alocar memória extra.

def contar_ate(n):
    for i in range(n):
        yield i

for numero in contar_ate(5):
    print(numero)  # Saída: 0 1 2 3 4

Vantagens em Memória e Performance

Generators brilham com dados grandes: uma lista de 1 milhão de números usa ~80MB, mas o generator equivalente consome apenas 128 bytes, gerando itens sob demanda. São nativos para for, list(), sum() e economizam tempo em cenários reais como leitura de logs ou CSV gigantes.

Expressões geradoras simplificam ainda mais: (x**2 for x in range(1000000)) cria um generator conciso sem parênteses extras, consumido iterativamente.

Tratamento de Exceções Simples

Exceções funcionam naturalmente dentro do generator. Use try/except para capturar erros durante a geração, como valores inválidos em um parser de JSON, mantendo o fluxo seguro.

def numeros_validos(arquivo):
    with open(arquivo) as f:
        for linha in f:
            try:
                yield int(linha.strip())
            except ValueError:
                print(f"Ignorando linha inválida: {linha}")
                continue

Isso previne crashes em dados reais “sujos”, comuns em automações e ETLs.

Fechamento Correto de Resources

Generators com arquivos ou conexões precisam de fechamento para evitar vazamentos. Use try/finally internamente ou generator.close() externamente, garantindo liberação automática.

# Exemplo 1: try/finally interno + close() externo
def ler_arquivo(arquivo):
    f = open(arquivo)
    try:
        for linha in f:
            yield linha.strip()
    finally:
        f.close()

gen = ler_arquivo('dados.csv')
try:
    for dado in gen:
        processar(dado)
finally:
    gen.close()

Context managers (with) integram perfeitamente para automação, eliminando necessidade de close() manual.

# Exemplo 2: Context manager with (mais limpo e automático)
def ler_arquivo_with(arquivo):
    with open(arquivo) as f:
        for linha in f:
            yield linha.strip()

# Uso simples e seguro - fecha automaticamente
for dado in ler_arquivo_with('dados.csv'):
    processar(dado)

Aplicações Práticas Iniciais

  • Arquivos gigantes: Leia linha por linha sem carregar tudo.
  • Sequências infinitas: Fibonacci ou contadores sem fim.
  • Pipelines simples: Filtre e transforme dados em cadeia.
  • Testes unitários: Mock de iteradores sem dados reais.​

Esses padrões otimizam ERPs, scripts de automação e APIs desde o primeiro projeto.

Conclusão

Generators transformam código Python em soluções eficientes e elegantes. Comece substituindo listas por generators em seus loops e veja a diferença em performance imediatamente.

Teste os exemplos acima no seu próximo projeto Python, meça o uso de memória com sys.getsizeof() e compartilhe seus resultados em @riverfount@bolha.us para discutirmos otimizações reais juntos!

 
Read more...

from Riverfount

Você já parou para pensar por que seu código Python consome cada vez mais memória em aplicações de longa duração, mesmo sem vazamentos óbvios? Palavras-chave como “garbage collector Python”, “contagem de referências Python”, “ciclos de referência Python” e “otimização de memória CPython” dominam buscas de desenvolvedores que enfrentam pausas inesperadas, inchaço de heap ou serviços que “incham” ao longo do tempo. Neste guia técnico expandido e atualizado, um engenheiro especialista em Python mergulha nos mecanismos internos do GC do CPython – com exemplos práticos de código, benchmarks reais e dicas avançadas de tuning – para você dominar a gestão de memória, detectar vazamentos sutis, configurar gerações otimizadas e escalar aplicações de produção sem surpresas.

Visão geral da memória no CPython

Python (implementação CPython) representa praticamente tudo como objetos alocados no heap: inteiros pequenos, strings, listas, funções, frames de pilha, módulos etc. Cada objeto PyObject carrega metadados essenciais, incluindo um contador de referências (ob_refcnt), tipo (ob_type) e, para contêineres rastreados pelo GC, ponteiros para listas duplamente ligadas das gerações (gc_next, gc_prev).

O ciclo de vida completo é: alocação via PyObject_New → incremento de refcount em referências → possível promoção geracional → detecção de ciclos ou refcount=0 → tp_dealloc (chama del se aplicável) → PyObject_Free. Objetos imutáveis como tuples pequenos ou strings interned podem ser otimizados pelo PGO (Python Object Generalizer), mas o GC foca em contêineres mutáveis.

CPython usa duas camadas complementares: contagem de referências (imediata, determinística) + GC geracional (para ciclos raros, probabilístico).

Contagem de referências: O coração do gerenciamento

O mecanismo primário é contagem de referências: Py_INCREF() em toda atribuição/nova referência, Py_DECREF() em remoções/saídas de escopo. Ao zerar, tp_dealloc é imediato, sem pausas.

  • Cenários de INCREF: atribuição (a = obj), inserção em lista/dict (lst.append(obj)), passagem por parâmetro, cópia forte.
  • Cenários de DECREF: del var, variável sai de escopo, lst.pop(), dict del, ciclo de vida de frames locais termina.
  • Overhead baixo: Operações atômicas em 64-bit, mas visíveis em workloads intensivos (ex.: loops com listas mutáveis).

Exemplo simples de contagem de referências

import sys
import tracemalloc

tracemalloc.start()  # Para medir alocações reais

a = []          # PyList_New → refcount=1
print(f"Refcount inicial: {sys.getrefcount(a)}")  # >=2 (a + getrefcount)

b = a           # Py_INCREF → refcount=2
print(f"Após b=a: {sys.getrefcount(a)}")

del b           # Py_DECREF → refcount=1
print(f"Após del b: {sys.getrefcount(a)}")

del a           # Py_DECREF → 0 → tp_dealloc imediato
print(tracemalloc.get_traced_memory())  # Memória liberada
tracemalloc.stop()

sys.getrefcount infla +1 pela referência temporária da função. Use ctypes para refcount “puro” se necessário.

O problema: ciclos de referência e por que falham

Contagem falha em ciclos: A→B→A mantém refcounts >0 mutuamente, mesmo se o “root” externo foi deletado. Comum em árvores bidirecionais, caches LRU com backrefs, grafos.

Exemplo expandido de ciclo com diagnóstico

import gc
import weakref

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None
        self._weak_next = None  # Weakref para evitar ciclo artificial

a = Node(1)
b = Node(2)

a.next = b
b.next = a  # Ciclo forte

print(f"Antes del - refcount a: {sys.getrefcount(a)}, b: {sys.getrefcount(b)}")
del a, b
print(f"Após del - ainda vivos! refcount a: {sys.getrefcount(a)}, b: {sys.getrefcount(b)}")

Weakrefs (unilateral) quebram ciclos sem custo de GC: weakref.ref(other)().

Coletor geracional: Detecção probabilística de ciclos

CPython adiciona GC apenas para contêineres (list, dict, instances, sets, etc. com tp_traverse/tp_clear). Não rastreia ints, floats, strings. Hipótese geracional: 90%+ objetos morrem jovens; sobreviventes são longevos.

4 Gerações reais (não documentadas publicamente): gen0 (jovem), gen1, gen2 (velhas), permanent (estáticas como módulos). Novos contêineres vão para gen0 via PyList_Append etc.

Limiares e disparo expandido

Thresholds padrão: (700, 10, 10) – gen0 dispara a cada ~700 alocs líquidas; a cada 10 coletas gen0, coleta gen1; a cada 10 gen1, full GC. Incremental GC (Python 3.13+?) limpa frações da gen2 por ciclo.

import gc

print("Thresholds:", gc.get_threshold())  # (700, 10, 10)
print("Contagens atuais:", gc.get_count())  # (gen0, gen1, gen2)

gc.set_threshold(1000, 20, 20)  # Menos pausas para throughput
print("Novos thresholds:", gc.get_threshold())

Sobreviventes sobem geração; gen2 é “quase permanente”.

Algoritmo de detecção de ciclos: Mark & Sweep otimizado

Fases (gc_collect_region):

  1. Roots → Reachability: De raízes (globals, stack, registers), traverse contêineres marcando alcançáveis (BFS via tp_traverse).
  2. Tentative Unreachable: Move suspeitos para lista separada; re-traverse para reviver falsos positivos.
  3. Finalizers & Clear: Chama tp_finalize/tp_clear em ciclos confirmados; DEALLOC se possível.

Ciclos puramente internos são liberados mesmo com refcount>0. gc.garbage guarda “uncollectable” com del pendente.

Usando o módulo gc na prática avançada

Monitoramento e debugging em produção

import gc
import objgraph  # pip install objgraph (opcional, para histograms)

print("Stats por geração:", gc.get_stats())
print("Objetos rastreados:", len(gc.get_objects()))

# Simular ciclo e coletar
def create_cycles(n=100):
    for _ in range(n):
        a = []; b = []; a.append(b); b.append(a)

create_cycles()
print(f"Antes GC: {len(gc.get_objects())}")
collected = gc.collect(2)  # Full GC
print(f"Coletados: {collected}, Garbage: {len(gc.garbage)}")
# objgraph.show_most_common_types()  # Se instalado

gc.callbacks para hooks em coletas; gc.is_tracked(obj) para checar.

Exemplo: Hunting vazamentos em loop

import gc
import time

def leaky_loop():
    cache = {}
    while True:
        obj = {'data': list(range(10000))}  # Simula dados grandes
        cache[id(obj)] = obj  # "Vazamento" intencional
        if len(gc.get_objects('dict')) > 10000:
            gc.collect()
            print("GC forçado, objetos:", len(gc.get_objects()))

# Em produção: rode com gc.set_debug(gc.DEBUG_LEAK)

Ajustando o GC para workloads específicos

Alto throughput (FastAPI/Flask): Thresholds altos + disable em endpoints rápidos. Data/ML (Pandas/NumPy): Desabilite GC (NumPy usa refcount puro). Long-running (Celery/services): Monitore gc.get_count(); tune baseado em RSS.

import gc
import signal

def tune_gc():
    gc.disable()  # Para bursts
    gc.set_threshold(0, 0, 0)  # Desabilita thresholds

# Context manager custom
class GCOff:
    def __enter__(self):
        gc.disable()
    def __exit__(self, *args):
        gc.enable()
        gc.collect()

Benchmarks reais: GC pausas <1ms em gen0; full GC pode ser 10-100ms em heaps grandes – meça com gc.get_stats().

Pontos práticos para engenheiros Python

  • **Evite del em ciclos**: Use contextlib ou weakrefs; del bloqueia auto-liberação.
  • Context managers > GC: Sempre with open(), with conn: para files/sockets – GC não garante ordem/timing.
  • **Slots e slots**: Reduzem dict interno (economia ~20-30% memória em classes).
  • Prod monitoring: Integre memory_profiler, fil, gc.get_objects() em healthchecks.
  • PyPy/Jython: Diferentes GCs (tracing); migração requer re-tune.

Conclusão e Próximos Passos

Dominar o GC do CPython transforma você de “esperando pausas” para “engenheiro proativo”: thresholds tunados cortam latência 20-50%, weakrefs eliminam 90% ciclos, e monitoramento previne OOMs em prod. Teste esses exemplos no seu código agora – rode gc.set_debug(gc.DEBUG_STATS) e veja o impacto real.

 
Read more...

from Riverfount

Baseado na Live de Python #150 do canal Eduardo Mendes no YouTube, este artigo explora de maneira prática e direta a “primitive obsession” — code smell onde tipos primitivos (strings, dicts, lists) substituem abstrações de domínio ricas — e como dataclasses (Python 3.7+, PEP 557) oferecem solução definitiva para criar Value Objects tipados, imutáveis e comportamentalmente ricos. Para sêniores buscando elevar modelagem DDD e reduzir technical debt em escala.

Primitive Obsession: Raiz do Problema

Primitive obsession ocorre quando entidades de domínio são reduzidas a “bags of primitives”, i. e., em que objetos de domínio são representados apenas como coleções simples de tipos primitivos (como strings, números, listas e dicionários) sem encapsulamento ou comportamento. Ou seja, em vez de ter classes ou estruturas que representem conceitos ricos com regras, validações e métodos, o código manipula “sacos” ou “pacotes” de dados primitivos soltos, o que aumenta a complexidade, propensão a erros e dispersa a lógica de negócio. Aplicando princípios de POO fundamentais: encapsulamento, polimorfismo e responsabilidade única. Consequências incluem:

  • Lógica de domínio espalhada: Funções utilitárias fazem parsing manual de strings/dicts
  • Falta de invariants: Sem validação, DDDs inválidos ou CPFs malformados passam despercebidos
  • Sem comportamentos: nome_completo() vira função externa poluente
  • Type unsafety: KeyError em runtime, mypy cego para estruturas aninhadas
  • Testabilidade pobre: Mockar dicts vs mockar objetos com dependências claras
# ANTES: Primitive Obsession Clássica
dados = [
    {"nome": "João", "sobrenome": "Silva", "telefone": {"residencial": "1234-5678"}, "ddd": 11},
    {"nome": "Maria", "sobrenome": "Santos", "telefone": {"residencial": "9876-5432"}, "ddd": 99}  # DDD inválido!
]

# Lógica espalhada + runtime errors
def nome_completo(dado: dict) -> str:
    return f"{dado['nome']} {dado['sobrenome']}"

def telefone_residencial(dado: dict) -> str:
    return dado['telefone']['residencial']  # KeyError se faltar!

nomes = [nome_completo(d) for d in dados]
telefone = telefone_residencial(dados[0])  # Fragile!

Custo real: Cyclomatic complexity explode, coverage cai, onboarding demora 3x mais.[9][10][1]

Dataclasses: Antídoto Completo à Primitive Obsession

Dataclasses não são “namedtuples mutáveis”. São geradores de Value Objects que restauram domínio perdido via automação OO inteligente:

1. Encapsulamento Automático + Comportamento

from dataclasses import dataclass, field
from typing import ClassVar
from datetime import date

@dataclass(frozen=True)
class Telefone:
    residencial: str
    
    def formatado(self, ddd: int) -> str:
        return f"({ddd}) {self.residencial}"

@dataclass(frozen=True)
class Pessoa:
    nome: str
    sobrenome: str
    telefone: Telefone
    ddd: int
    data_nascimento: date
    
    # Invariant via post_init
    def __post_init__(self):
        if self.ddd < 11 or self.ddd > 99:
            raise ValueError(f"DDD inválido: {self.ddd}")
        if self.data_nascimento > date.today():
            raise ValueError("Data futura inválida")
    
    @property
    def nome_completo(self) -> str:
        return f"{self.nome} {self.sobrenome}"
    
    @property
    def telefone_formatado(self) -> str:
        return self.telefone.formatado(self.ddd)
    
    @property
    def idade(self) -> int:
        return (date.today() - self.data_nascimento).days // 365
    
    # Value Object equality por valor, não referência
    def __eq__(self, other):
        if not isinstance(other, Pessoa):
            return NotImplemented
        return (self.nome_completo, self.ddd) == (other.nome_completo, other.ddd)

2. Uso: Domínio Restaurado, Zero Boilerplate

# Value Objects puros, type-safe
pessoas = [
    Pessoa("João", "Silva", Telefone("1234-5678"), 11, date(1990, 5, 15)),
    Pessoa("Maria", "Santos", Telefone("9876-5432"), 21, date(1985, 8, 22))
]

# Comportamento encapsulado, sem loops manuais
print([p.nome_completo for p in pessoas])
# ['João Silva', 'Maria Santos']

print(pessoas[0].telefone_formatado)  # "(11) 1234-5678"
print(pessoas[0].idade)  # ~35

# Validação em tempo de construção
try:
    Pessoa("Inválido", "X", Telefone("0000"), 5, date(2026, 1, 1))
except ValueError as e:
    print(e)  # "DDD inválido: 5"

3. Integrações Avançadas

from dataclasses import asdict, astuple
import json
from typing import Any

# Serialização controlada (não expõe internals)
def pessoa_to_json(p: Pessoa) -> str:
    return json.dumps({
        "nome_completo": p.nome_completo,
        "telefone": p.telefone_formatado,
        "idade": p.idade
    })

# mypy + Pydantic validation
# pip install pydantic[email-validator]
from pydantic import BaseModel, validator

class PessoaPydantic(BaseModel):
    nome_completo: str
    telefone: str
    idade: int
    
    @validator('telefone')
    def validate_telefone(cls, v):
        if not v.startswith('('):
            raise ValueError('Formato inválido')
        return v

Benefícios Arquiteturais Profundos

Aspecto Primitive Obsession Dataclasses Value Objects
Encapsulamento 0% (dicts públicos) 100% (métodos + invariants)
Type Safety Runtime KeyError Compile-time + runtime
Testabilidade Mock dicts complexos Mock objetos com dependências claras
Performance ~1x (dict access) ~1.2x (dataclass overhead mínimo)
Serialização json.dumps(dict) asdict() + versionamento
Extensibilidade Refatorar todos consumers[¹] Herança + composição

[1] Dificuldade que ocorre quando a estrutura de dados primitivos é modificada ou evoluída.

Métricas reais: Times reportam 40% menos bugs de dados, 25% mais velocidade em reviews.

Primitive Obsession em Escala: O Verdadeiro Custo

# Microsserviço com 50+ endpoints primitivos
@app.get("/clientes")
def listar_clientes():
    return [
        {"id": 1, "nome": "João", "email": "joao@email.com", "ativo": True},
        # 100+ campos espalhados, validação em middlewares...
    ]

Virada com dataclasses:

@dataclass(frozen=True)
class Cliente:
    id: int
    nome_completo: str
    email: EmailStr  # pydantic
    status: ClienteStatus  # Enum
    
    @classmethod
    def from_db(cls, row: Row) -> "Cliente":
        return cls(
            id=row.id,
            nome_completo=...,
            email=row.email,
            status=ClienteStatus.from_str(row.status)
        )

Conclusão

Dataclasses eliminam primitive obsession restaurando domínio rico sem sacrificar performance ou a experiência do desenvolvedor. São o “sweet spot” entre namedtuples (imutáveis, sem comportamento) e classes manuais (boilerplate pesado).

 
Read more...

from Riverfount

A arquitetura hexagonal, ou Ports and Adapters, coloca a lógica de negócio no centro de um hexágono simbólico, cercada por portas (interfaces abstratas) que conectam adaptadores externos como bancos de dados, APIs web, filas ou serviços de terceiros. Proposta por Alistair Cockburn em 2005, ela inverte as dependências tradicionais: o domínio não conhece frameworks ou persistência, mas estes dependem dele via injeção de dependências, promovendo código limpo e adaptável em Python. Essa abordagem alinha-se perfeitamente à filosofia “simples é melhor” do Python, mas com rigor para domínios complexos.

Componentes e Fluxo de Dados

O domínio abriga entidades imutáveis, agregados e regras puras, livres de anomalias arquiteturais como dependências de frameworks ou bancos de dados. Portas de entrada definem casos de uso (ex.: CreateUserPort), enquanto portas de saída expõem repositórios abstratos (ex.: UserRepository). Adaptadores concretos — como FastAPI para HTTP ou SQLAlchemy para DB — implementam essas portas, garantindo que o core permaneça intocado por mudanças externas. O fluxo entra pelas bordas, atinge o domínio via portas e sai por adaptadores, criando uma barreira unidirecional contra acoplamento.

Vantagens em Aplicações Python

  • Testes isolados e rápidos: Mockar portas permite TDD sem infraestrutura real, reduzindo flakiness em CI/CD com pytest.
  • Flexibilidade tecnológica: Troque Flask por FastAPI ou SQLite por DynamoDB sem refatorar o core, ideal para microsserviços.
  • Escalabilidade e manutenção: Suporta DDD em equipes grandes.
  • Longevidade: Evolução sem rewrites totais, perfeita para ERPs ou APIs corporativas em Python.

Desvantagens e Limitações

  • Curva de aprendizado e boilerplate: Abstrações iniciais sobrecarregam protótipos ou CRUD simples, violando “menos é mais” em Python.
  • Over-engineering em projetos triviais: Para apps sem domínio rico, aumenta complexidade sem ROI; prefira MVC tradicional.
  • Manutenção de portas: Muitas interfaces podem virar “abstrações vazias” se não gerenciadas, confundindo desenvolvedores menos experientes.

Casos de Uso Práticos em Python

Adote em microsserviços serverless. Útil em sistemas DDD como bibliotecas de gestão ou ERPs, isolando regras fiscais de persistência multi-DB. Evite em scripts ou MVPs rápidos; combine com Clean Architecture para monolitos legados. Exemplos reais incluem APIs de usuários com FastAPI, em que trocar mocks por Redis em produção é trivial.

Refatorando Projetos Existentes para Hexagonal

Refatorar monolitos Python para hexagonal exige abordagem incremental, priorizando estabilidade e testes. Siga estes passos práticos:

  1. Mapeie o Domínio Atual: Identifique entidades principais (ex.: User, Order) e regras de negócio misturadas em controllers/services. Extraia para domain/entities/ e domain/services/, criando dataclasses imutáveis.
  2. Defina Portas Mínimas: Para cada operação externa (DB, email, HTTP), crie interfaces ABC em domain/ports/ (ex.: UserRepository, EmailPort). Comece com 2-3 portas críticas.
  3. Crie Casos de Uso: Migre lógica de controllers para application/use_cases/ injetando portas. Exemplo: CreateUserUseCase(repo: UserRepository) orquestra validação e domínio.
  4. Implemente Adaptadores Gradualmente: Refatore controllers existentes para usar casos de uso (adaptadores de entrada). Crie infrastructure/repositories/ com implementações atuais (SQLAlchemy → PostgresUserRepository).
  5. Reestruture Pastas: Adote src/domain/, src/application/, src/infrastructure/. Use dependency-injector para wiring automático em main.py.
  6. Testes como Rede de Segurança: Escreva testes para portas/mock antes de refatorar, garantindo 100% de cobertura no domínio. Rode pytest em paralelo durante a migração.
  7. Migre por Feature: Refatore uma entidade por sprint (ex.: só User primeiro), mantendo código legado rodando via adaptadores híbridos.youtube​

Use a ferramenta radon para monitorar a complexidade ciclomática do código (ex.: radon cc src/). Se a média subir acima de 10 ou o ROI (retorno sobre investimento) cair — medido por testes mais lentos ou refatorações frequentes —, pause a migração. Projetos grandes (>10k linhas de código) recuperam o investimento em 3-6 meses com testes mais rápidos e manutenção simplificada; para projetos pequenos (<5k linhas), calcule o custo-benefício antes de prosseguir.

Conclusão: Adote Hexagonal e Eleve Seu Código

A arquitetura hexagonal transforma aplicações Python em sistemas resilientes, testáveis e evolutivos, isolando o domínio de frameworks voláteis e promovendo baixa manutenção a longo prazo. Ideal para domínios complexos como ERPs ou microsserviços, ela equilibra a simplicidade zen do Python com escalabilidade enterprise.​

Pronto para refatorar? Comece hoje: pegue um módulo CRUD do seu projeto, extraia as portas em 1 hora e teste com pytest. Baixe repositórios de exemplo no GitHub, experimente em um branch e veja a diferença em testes isolados. Compartilhe conosco no Mastodon em @riverfount@bolha.us sua primeira vitória hexagonal — ou dúvidas para discutirmos.

 
Read more...

from Outras Coisas Mais

Oi! Estou procurando por uma amiga. Pode me ajudar?

Acho que talvez você a conheça:

Ela tem um sorriso gracioso; uma presença radiante; uma personalidade marcante, Ela gosta de dançar na chuva, o que é incrível visto o jeitinho de Paty — mas é só de primeira vista; usa uns vestidos lindos e tem um caminhar tão envolvente; A observar na contra luz do por do sol é de uma magia indescritível e os olhos, ah, aqueles olhos — são como uma doce e terna melodia. E por falar em melodia... O modo dela falar é delicioso, você pode ficar horas a ouvindo; ela tem uma forma curiosa de conversar sobre a vida e uma mania de terminar as frases com “Sabe?” que é tão maravilhoso de ouvir; e é o máximo como ela pronuncia “fácil”; O gosto para filmes é meio estranho; É maconheira; Gosta de ler para viajar a outros mundos— não é o máximo alguém que gosta de ler? E por incrível que pareça, ela gosta dos meus poemas. Ou parecia gostar. Ela tem alergia a pimenta; faz umas panquecas que parecem ser muito, muito saborosas; gosta de jabuticabas e coisas com canela — já falei das panquecas? E não gosta de lavar a louça, principalmente panelas; Ela é Bartender; gosta de jogar bilhar; virar shots e apreciar drinks — lembro de um tal Fritzgerald. Costuma ser um tanto distante, excessivamente focada no trabalho e ultimamente, tem parecido menos afetuosa — ou talvez seja um equívoco meu. Mas eu entendo esse jeito dela ser, e amo esse jeito dela ser.

Ah! Ela é inteligentíssima e escreve textos maravilhosos — que estou com saudade de ler.

Viu ela por aí? Se a vir, diga que mandei um abraço e que estou com saudade. E que quando ela puder e quiser, que me dê notícias de como está se sentindo.

Carta enviada para amiga
 
Leia mais...

from in.versos

Dengo, esta canção é pra você entender.

Não há confusão, eu sei bem.

Não quero tua vida; basta-me tua companhia, teu “olá”, teu “bom dia”.

Dengo, o meu amor não tá pra venda, não pede nada em troca.

Se te dei, é teu — apesar de meu.

Mas, se não quiser, basta dizer: eu vou entender.

Só peço que não me culpe por sentir o que sinto por você.

 
Leia mais...

from in.versos

Dá-me de comer aos vermes. Que eles se empanturrem com minha carne podre.

Que devorem olhos e língua — pois já não preciso deles senão para vê-la e lisonjeá-la.

Que se fartem de meu coração — pois já não necessito viver, tampouco sentir.

Mas antes de voltar ao pó terei primeiro eu que sucumbir.

Não te preocupes em sujar as mãos — essa tarefa cumprirei eu mesmo:

afogando-me em goles de poesias e garrafas de desejo.

 
Leia mais...

from Paulo Henrique Rodrigues Pinheiro

Clara Charf

Morreu ontem, 3 de novembro de 2025, aos 100 anos, a camarada Clara Charf, militante revolucionária forjada nas lutas comunistas desde a juventude. Nascida em 17 de julho de 1925, Clara ingressou no Partido Comunista Brasileiro (PCB) ainda adolescente, tornando-se uma das vozes ativas na resistência à ditadura do Estado Novo.

Casada com Carlos Marighella, com quem compartilhou não apenas a vida afetiva, mas também o profundo compromisso com a transformação social, Clara teve uma longa carreira na estrutura partidária do velho PCB. Utilizando suas prerrogativas profissionais como aeromoça da Panair do Brasil, desempenhou papel crucial na logística da militância, transportando documentos clandestinos do Partidão por diversas cidades, cruzando fronteiras e arriscando a própria liberdade em nome da organização revolucionária.

Além dessa atuação discreta porém fundamental, Clara participou ativamente de inúmeras outras tarefas revolucionárias – desde a organização de bases operárias até a coordenação de ações de solidariedade internacionalista. Após o golpe de 1964, aprofundou seu engajamento na resistência à ditadura militar, tornando-se uma das fundadoras da Ação Libertadora Nacional (ALN), organização da qual Marighella seria principal dirigente.

Sua trajetória não se limitou à militância armada. Com a redemocratização, Clara Charf seguiu na linha de frente das lutas sociais, dedicando-se especialmente à causa feminista e tornando-se uma das principais referências do movimento de mulheres no Brasil. Foi fundadora da União de Mulheres de São Paulo e esteve envolvida nas campanhas pela Anistia ampla, geral e irrestrita e na construção do Partido dos Trabalhadores.

 
Leia mais...

from Fure a bolha

#tecnologia #tecnootimismo #IA

Introdução

Há uma ideia muito errada de que inteligência artificial seria juiz confiável e imparcial. Tem relato na rede social Lemmy de um infeliz cuja namorada vive jogando as brigas no casal no ChatGPT para dizer que o cara estava errado mesmo, o ChatGPT confirmou.

Na minha área (TI), antigamente era bem mais comum as pessoas lerem manuais info; man pages; e livros sobre algoritmos, matemática, engenharia de software. Aí quando se popularizaram sítios como Stack Overflow, alguns desenvolvedores passaram a copiar trechos prontos desses sítios para a base de código sem entender plenamente o funcionamento. Hoje piorou – o povo chupa da IA códigos grandes sem entender nada de como funciona. Menos conhecimento ainda do código que a pessoa terceirizou, e maior chance de erros (IA usando fontes ruins) e alucinações (IA fantasiando).

A seguinte entrevista de Jeremy Howard traz excelente perspectiva sobre o uso responsável de IA: https://youtu.be/LrFbxIvsipw

Entrevista com Jeremy Howard

Jeremy é CEO da Fast.ai e da Answer.ai. Criou o primeiro LLM – Large Language Model, ou modelo de linguagem de grande escala. Esse modelo, chamado ULMFiT, mostrou que o deep learning (aprendizagem profunda) funcionava muito bem com linguagem natural, e foi a base do ChatGPT (hoje temos Deep Seek).

No vídeo ele contesta a empolgação com agentes de IA (AI agents). Contesta a ideia de que a IA fará tudo para as pessoas. Argumenta que quem mergulha em agentes pára de aprender. Não pratica suas habilidades. Terceiriza tudo. Jeremy pretende estar no grupo de pessoas que usa IA com muito cuidado para continuar melhorando suas habilidades. Ele usa IA para se aprimorar, ganhar competência, aprender mais, praticar melhor.

Pessoas estão esquecendo como trabalhar. Estão esquecendo que conseguem trabalhar. Se a IA não resolve, ficam perdidas. Isso faz mal à psique. Quem deveria programar software, usa IA para criar milhares de linhas de código que não entende. Isso acumula dívida tecnológica, tornando muito difícil a depuração de defeitos e a integração. Jeremy já viu pessoas se tornarem deprimidas percebendo que perderam a competência e o controle. A abordagem centrada em agentes de IA coloca o computador no controle. Quem faz isso se coloca no caminho de se tornar incompetente e obsoleto.

O uso cego de IA pode aumentar no curto prazo o volume de trabalho, mas reduz a produtividade real a longo prazo. Código fonte gerado por IA não é muito bom. Não é bem integrado. Não cria camadas de abstração que funcionam bem em conjunto. A boa engenharia de software faz a produtividade aumentar ao longo do tempo. Com código gerado por IA ocorre o oposto. Jeremy avalia que empresas que se apoiam cegamente em IA vão olhar para trás e perceber que, no esforço de sempre conseguir resultado rápido em duas semanas, destruíram sua competência organizacional de criar coisas que duram.

Jeremy propõe que a IA observe o trabalho humano, dê dicas e responda perguntas de modo a guiar nosso trabalho, ao invés de executá-lo. Ele avalia ser um desenvolvedor de software muito melhor do que dois anos atrás, pois se dedica a usar IA para se aprimorar. Quer superar a IA.

Jeremy também apoia fortemente o código aberto, inclusive na IA, pelo bem da democracia. O poder deve ser decentralizado e não concentrado nas mãos dos ricos e poderosos. O Estado precisa intervir. Instituições privadas têm um papel de aproveitar os mercados.

No momento a China é o país que está nos salvando da centralização. Hoje todos os melhores modelos de IA de código aberto são chineses. Jeremy passou muito tempo na China, e avalia que lá o sistema investe de verdade em ciência da computação e matemática, e muitas pessoas acreditam na abertura.

Conclusão

Considero que é crítica a habilidade de usar IA de maneira responsável, para aprimorar o trabalhador humano e não substituí-lo. No entanto, falar é mais fácil do que fazer. Assim como sabemos que alimentação balanceada é muito importante para saúde, mas é difícil resistir à tentação de comer fast food e ultraprocessados, também é difícil resistir à tentação de terceirizar o trabalho para IA.

Eu aqui tento conferir e entender o que o Deep Seek diz. Por privacidade instalei também o qwen2.5:3b localmente em um contêiner Podman no meu laptop. Pretendo comprar uma máquina mais parruda para rodar localmente modelos mais poderosos e reduzir o uso de modelos hospedados em outros países. E quando preciso usar modelos hospedados em outros países, dou forte preferência a modelos de código aberto e hospedados em um país amigo como a China.

 
Leia mais...

from Fure a bolha

#ciencia #jornalismo #historia

Fontes confiáveis

Introdução

Na ciência da computação dizemos em inglês garbage in, garbage out – se a entrada é lixo, a saída é lixo. Isso vale também para argumentação lógica e aprendizado. Conclusões verdadeiras exigem não apenas um método válido mas também fontes confiáveis. Podemos aplicar metaforicamente o ditado “você é o que você come”.

Critério 1: fidelidade a fatos (literatura científica)

Para qualidade de fonte jornalística ou histórica, um critério crucial é fidelidade aos fatos, respeito ao consenso da literatura científica e uso de bons métodos e boas fontes. Isso importa mais que o tamanho e o prestígio do veículo.

Muito me inspira o argumento de Santo Agostinho – um dos maiores doutores da Igreja. Ele, da época do Império Romano, já defendia que os cristãos se informassem bem sobre a ciência secular. Argumentava (parafraseando): quando um infiel ouve um cristão dizer asneiras e barbaridades sobre coisas visíveis, a Igreja perde credibilidade. De fato, quem diz barbaridades até sobre coisas visíveis não tem credibilidade nem para coisas visíveis, nem (muito menos) para coisas invisíveis.

Isso me serve de valiosa analogia para fontes jornalísticas e históricas. Meu ponto forte são as exatas, pois sou formado em engenharia eletrônica e ciência da computação. Penso que as ciências humanas e sociais são mais sutis e mais suscetíveis a controvérsia do que as ciências exatas. Então quem diz asneiras e barbaridades até sobre as exatas, que dirá das humanas? Quando a cegueira ideológica não poupa nem as exatas, aquela fonte não é confiável nem para exatas, e muito menos para humanas e sociais.

Por exemplo, quem nega a ciência climática perde a credibilidade.

Critério 2: autoridade

Normalmente, quem consulta um cardiologista renomado não exige as fontes (artigo científico, livro etc) quando ele diagnostica pressão alta. Da mesmo forma, um especialista em determinado assunto fala com autoridade sobre aquele assunto. Já um anônimo tem necessidade reforçada de mostrar boas fontes para suas principais alegações controversas. Das fontes sugeridas abaixo, o sítio Red Sails é pouco conhecido mas ainda assim é confiável, pois veicula artigos de autores bem formados ou cobertos de citações de boas fontes.

Critério 3: enquadramento

O enquadramento (ou framing) é uma arma poderosa. Vai além da factualidade e direciona a seleção, ênfase e apresentação de aspectos de uma realidade, promovendo uma definição particular do problema, uma interpretação causal, uma avaliação moral ou uma recomendação de conduta.

Não se trata apenas do que é noticiado, mas como. A escolha de palavras, a voz gramatical (ativa ou passiva), a atribuição (ou não) de autoria e a adjetivação criam um “quadro” mental que induz a interpretação do leitor. Um mesmo acontecimento pode ser enquadrado como “ataque brutal” ou “auto defesa”.

A seguir, contrastamos manchetes do jornal estadunidense The New York Times, ilustrando a diferença drástica de enquadramento conforme os interesses geopolíticos dos Estados Unidos.

Enquadramento desfavorável e atributivo

The New York Times: Russia Strikes Children’s Hospital in Deadly Barrage Across Ukraine

Russia Strikes Children’s Hospital in Deadly Barrage Across Ukraine

Para um adversário dos EUA, o jornal emprega técnicas para gerar condenação e imputar brutalidade a um responsável específico:

  • Identificação clara do agressor: “Russia Strikes” (“Rússia ataca”). As primeiras palavras da manchete explicitam o responsável pelo ataque.
  • Voz ativa: O emprego da voz ativa (”Russia Strikes”) ressalta a intenção. Apresenta o adversário dos EUA como um ator que escolheu a violência.
  • Linguagem emotiva e conotativa: “Deadly Barrage” (“ataque massivo mortal”). A palavra “barrage” enfatiza um volume massivo e indiscriminado de ataques, enquanto “deadly” enfatiza o custo humano.

Enquadramento favorável e dissociativo

The New York Times: Israel-Hamas War: At Least 25 Reported Killed in Strike on School Building in Southern Gaza

Israel-Hamas War: At Least 25 Reported Killed in Strike on School Building in Southern Gaza

Em contraste gritante, o jornal enquadra o ataque israelense a uma escola palestina empregando técnicas para diluir a responsabilidade e contextualizar o ato de forma favorável:

  • Contextualização que justifica: “Israel-Hamas War” (guerra entre Israel e Hamas). A manchete insere o evento no contexto amplo de uma “guerra”. Isto, por um lado, informa, mas por outro, insinua a normalidade do acontecimento, como se fosse um resultado esperado e inevitável da guerra.
  • Voz passiva e agente oculto: “At Least 25 Reported Killed” (“pelo menos 25 mortos são reportados”). A voz passiva omite o autor do ato. As vítimas (“25”) tornam-se o sujeito gramatical da frase, enquanto o ator das mortes desaparece do texto.
  • Dissociação do agressor: A manchete omite Israel como o autor do ataque. O leitor desatento pode, inclusive, inferir que possa ter sido o Hamas. O jornal descreve o ataque como um evento fortuito.
  • Linguagem técnica e impessoal: “Strike on School Building” (Ataque a Edifício Escolar). Comparado com “Strikes a Children's Hospital in Deadly Barrage”, a linguagem é mais fria e burocrática, minimizando a carga emocional.

Impacto do enquadramento

Os veículos jornalísticos sistematicamente enquadram acontecimentos conforme sua visão de mundo e seus interesses. A cobertura jornalística pode, por meio de escolhas conscientes ou inconscientes, orientar a percepção do público:

  • No primeiro exemplo, o veículo induz o leitor a condenar um agressor claramente identificado por um ato de brutalidade.
  • No outro exemplo, o veículo induz o leitor a registrar um evento trágico, mas sem um agressor claro, num contexto de “guerra” que atenua a responsabilidade.

Reconhecer esses mecanismos é vital para uma leitura crítica da mídia. A honestidade de um veículo vai além da factualidade, mas alcança o enquadramento da realidade:

  • Quem é apresentado como agente de ação e quem é apresentado como sujeito passivo?
  • O que é omitido? O que é enfatizado?
  • Que palavras são escolhidas para influenciar minha percepção?

Sugestões

Abaixo vão algumas sugestões de comunicadores e veículos que respeitam a ciência e, transparentemente, se posicionam à esquerda.

Língua portuguesa

  1. Opera Mundi
  2. Elias Jabbour – Presidente do Instituto Municipal de Urbanismo Pereira Passos. Professor Associado da Faculdade de Ciências Econômicas, do Programa de Pós-Graduação em Ciências Econômicas e do Programa de Pós-Graduação em Relações Internacionais na UERJ. Em 2023–2024 foi Consultor Sênior da Presidência do Banco do BRICS e, de abril de 2006 a fevereiro de 2007, Assessor Econômico da Presidência da Câmara dos Deputados. Graduado em Geografia (1997), Doutor (2010) e Mestre (2005) em Geografia Humana pela USP. Tem experiência em Geografia e Economia com ênfase em Geografia Humana e Econômica, Economia Política, Economia Política Internacional e Planejamento Econômico. Atua principalmente nos temas: China; Socialismo com Características Chinesas; Nova Economia do Projetamento; Categorias de Transição ao Socialismo; Estratégias e Experiências Nacionais e Comparadas de Desenvolvimento; Categoria Marxista de Formação Econômico-Social; e Pensamento Independente de Ignacio Rangel. Vencedor do Special Book Award of China.
  3. José Kobori, importante financista de esquerda
  4. ICL Notícias
  5. Carta Capital

Língua inglesa

  1. Geopolitical Economy Report
  2. Red Sails
 
Leia mais...

from Quadrinistas Uni-vos

No dia 23 de julho lançamos, em nosso blogo e aqui no perfil do Instagram, em conjunto com a FEPAL e o comitê árabe-brasileiro de solidariedade do Paraná, manifestação dirigida à organização da Bienal de Quadrinhos de Curitiba questionando se a vinda de Rutu Modan seria mesmo a melhor ideia, por tratar-se de figura israelense com posições chanceladas pela por Israel, justamente no momento histórico em que vivemos o primeiro genocídio televisionado da história, na Palestina (Gaza), e frente ao qual não cabe apoio tácito pelo silêncio.

Esta manifestação política motivou uma série de ações e discussões que proporcionaram um processo com olhar mais cuidadoso para a questão por parte da organização do evento. Inicialmente, pouco tempo após a manifestação de 23 de julho, a organização se posicionou em nota nas redes sociais (26 de julho), colocando-se abertamente contra o genocídio do povo palestino. Em seguida, o coletivo Quadrinistas Uni-vos foi convidado a participar de uma mesa de diálogo com a organização da Bienal, a FEPAL e o comitê de solidariedade ao povo palestino, onde houve consenso para que o povo palestino ocupasse papel de protagonismo na Bienal e pudesse falar a todos presentes, seja sobre quadrinhos, seja sobre a situação de horror vivida na Palestina, num contraponto à mídia hegemônica brasileira, sócia do regime sionista na empreitada genocida em Gaza.

Essa postura da organização da Bienal, solícita e gentil, além de coerente, permitirá construir uma bienal em que a utopia de um mundo em que o futuro para todos – e, neste momento, especialmente para o povo palestino – seja possível, com paz e justiça, em lugar de um não-futuro de esquecimento e total aniquilação. Que desta Bienal de Curitiba emitamos força para a retomada da paz e da soberania do povo palestino sobre a sua Terra Santa.

Coletivo Quadrinistas, Uni-vos FEPAL – Federação Árabe Palestina do Brasil Comitê Árabe brasileiro de Solidariedade – Paraná Comitê de Solidariedade à Palestina de Curitiba

 
Leia mais...