
🐍 Programação funcional em Python
Qualquer programador Python, mais cedo ou mais tarde, ouve: «experimenta escrever num estilo funcional, o teu código ficará mais limpo». Mas quando abres a documentação, vês map, filter e reduce com lambdas e não percebes por onde começar.
O problema não é a programação funcional ser difícil. O problema é que a maioria dos guias ou se perde em sebes académicas de Haskell ou se limita à sintaxe sem explicar o «porquê». O Python, sendo uma linguagem multiparadigma, fornece exatamente as ferramentas funcionais de que precisas para as tarefas do dia a dia, sem extremismos.
Neste material, uma análise prática das capacidades funcionais do Python: das compreensões de lista às funções lambda, com exemplos reais e cenários onde cada ferramenta simplifica genuinamente o código.
💡 Visão geral rápida:
- Decompor compreensões de lista, porque são melhores do que ciclos e quando as usar
- Dominar geradores:
yield, poupança de memória e expressões geradoras - Percorrer
map+filter+reduce: exemplos práticos de substituição de ciclos - Aprender a escrever funções lambda e perceber onde são adequadas e onde prejudicam a legibilidade
Programação funcional, do que estamos a falar
A programação funcional é uma abordagem onde um programa é construído a partir de funções matemáticas: estas recebem dados de entrada imutáveis e devolvem um resultado sem alterar nada externo. Sem efeitos colaterais, sem modificar o estado global.
Em linguagens funcionais puras como Haskell, as partes «puras» e «impuras» (que interagem com o mundo exterior) do programa são estritamente separadas. Esta abordagem permite a prova formal da correção do código: o compilador garante a ausência de efeitos colaterais inesperados.
O Python não foi concebido como uma linguagem funcional. É multiparadigma: orientado a objetos, procedural e, sim, também funcional. Escrever em Python num estilo puramente funcional é como martelar pregos com um microscópio: tecnicamente possível, mas desconfortável e pouco natural. No entanto, pegar em ferramentas funcionais individuais e integrá-las em código familiar é um padrão funcional e útil.
Compreensões de lista, a principal ferramenta funcional do Python
O pão com manteiga da programação funcional é trabalhar com listas. Selecionar elementos por condição, transformar cada elemento, construir uma nova lista a partir de uma existente, tudo isto em Python é coberto pelas compreensões de lista.
Antes, ciclo procedural:
1 def filter_odd(li): 2 result = [] 3 for i in li: 4 if i % 2 == 1: 5 result.append(i) 6 return result 7 8 print(filter_odd([2, 4, 6, 7, 8, 1, 19, 200, 42, 31]))
Depois, compreensão de lista:
1 li = [2, 4, 6, 7, 8, 1, 19, 200, 42, 31] 2 odd_numbers = [x for x in li if x % 2 == 1] 3 print(odd_numbers) # [7, 1, 19, 31]
Quatro linhas de código procedural comprimidas numa só. A legibilidade não sofreu: a sintaxe [expression for element in iterator if condition] é intuitivamente clara, «toma x de li se x for ímpar».
E isto não é apenas açúcar sintático. A compreensão de lista é executada mais rapidamente do que um ciclo equivalente com .append() porque é executada ao nível do C, e não ao nível do interpretador Python.
O que é um iterador
A compreensão de lista baseia-se no conceito de iterador, um objeto que devolve o próximo elemento de uma sequência quando lhe é pedido next(). Qualquer objeto que implemente o método __iter__ é designado iterável. Lista, string, tuplo, dicionário, conjunto, são todos iteráveis.
1 spam_iter = iter("foobar") 2 result = "".join([c.upper() for c in spam_iter]) 3 print(result) # FOOBAR
Aqui iter() devolve um iterador de string, e a compreensão de lista itera através dele, aplicando .upper() a cada caractere.
Geradores, sequências preguiçosas
Um iterador pode ser criado não só a partir de uma coleção existente, mas também usando uma função geradora. Em vez de return, usa yield: a função «adormece», lembrando o seu estado, e na próxima chamada de next() continua do mesmo ponto.
1 def gen(max_val): 2 i = 1 3 while i < max_val: 4 yield i 5 i += 1 6 7 g = gen(1000) 8 for _ in range(12): 9 print(next(g))
Este código irá imprimir números de 1 a 12. A função gen não cria uma lista de mil elementos em memória, gera valores um a um, sob demanda. Para sequências de milhões de registos (linhas de um ficheiro de log, stream de base de dados), a diferença no consumo de memória é de ordens de magnitude.
Expressões geradoras
Sintaxe compacta: o mesmo que as compreensões de lista, mas com parênteses em vez de parênteses retos.
1 g = (c.upper() for c in "foobar")
Isto não é um tuplo, é um gerador. A regra é simples: parênteses retos → lista (ávida, tudo em memória), parênteses curvos → gerador (preguiçoso, um elemento de cada vez).
Os geradores são uma ponte entre o estilo procedural e o funcional em Python: fornecem avaliação preguiçosa sem mergulhar na teoria das mónadas.
Map, filter e reduce: três pilares do processamento funcional
Três funções integradas que nas linguagens funcionais são a base de tudo. O Python implementa-as à sua maneira, e é importante conhecer as nuances das versões específicas do Python.
Map, aplicar uma função a cada elemento
1 def square(x): 2 return x * x 3 4 result = map(square, [1, 2, 3]) 5 print(list(result)) # [1, 4, 9]
map recebe uma função e uma sequência, e devolve um iterador com os resultados da aplicação da função a cada elemento. No Python 3, map devolve um iterador, não uma lista, pelo que precisas de list() para ver o resultado.
Para transformações simples, a compreensão de lista é muitas vezes mais legível:
1 [x * x for x in [1, 2, 3]] # same thing, but more familiar
Filter, selecionar elementos por condição
1 def is_upper(c): 2 return c == c.upper() 3 4 result = filter(is_upper, "FreedominObscureandoutlandishcOde") 5 print(list(result)) # ['F', 'O', 'O']
filter mantém apenas os elementos para os quais a função predicado devolve True. Tal como map, devolve um iterador no Python 3.
Reduce, reduzir uma sequência a um único valor
Ao contrário de map e filter, reduce não reside no âmbito integrado, mas no módulo functools. Aplica sequencialmente uma função aos elementos, acumulando o resultado.
1 from functools import reduce 2 3 def add(a, b): 4 return a + b 5 6 print(reduce(add, range(1, 6))) # 15
Nos bastidores: ((((1 + 2) + 3) + 4) + 5) = 15. Para somar números em Python existe a função integrada sum(), pelo que reduce é mais usado para reduções não padronizadas: construir uma árvore a partir de uma lista plana, fundir dicionários aninhados, calcular o máximo divisor comum de uma sequência.
Funções lambda, auxiliares anónimos
Quando uma função é necessária exatamente uma vez e o seu corpo cabe numa expressão, usa lambda:
1 lambda arguments: expression
O mesmo exemplo de filter pode ser reescrito sem um def separado:
1 result = list(filter(lambda c: c == c.upper(), "FreedominObscureandoutlandishcOde"))
Lambda é um compromisso. Vantagem: não é necessário declarar uma função separada para uma verificação trivial. Desvantagem: a legibilidade diminui se a expressão se tornar mais complexa do que um par de operações. Regra prática: se a lambda não couber numa linha ou começares a aninhar lambdas umas dentro das outras, extrai para um def normal.
Onde as lambdas são realmente úteis
O cenário mais comum é a ordenação por uma chave não padronizada:
1 users = [{"name": "Alice", "age": 31}, {"name": "Bob", "age": 25}] 2 users.sort(key=lambda u: u["age"])
Ou transformação rápida em map/filter quando o corpo é verdadeiramente trivial:
1 squares = list(map(lambda x: x * x, range(10)))
Mas se estiveres a pensar numa lambda para lógica com quatro ou cinco operações, para. Um nome de função funciona como documentação, e def fornece espaço para uma docstring.
⁉️🤔 Perguntas comuns
Quando é que uma compreensão de lista é melhor do que map?
Uma compreensão de lista como
[x*2 for x in data]lê-se de forma mais natural do quelist(map(lambda x: x*2, data)). Uma operação integrada com uma lambda tem dois níveis de indireção, enquanto uma compreensão de lista tem um. Mas se a função já existir como umdefseparado,mapcom ela é conciso. A compreensão de lista ganha em legibilidade para transformações simples, especialmente com filtragem através de[... if ...]. Para lógica complexa com variáveis intermédias, um ciclofornormal é imbatível.
Gerador ou lista, qual escolher?
Se o resultado for necessário várias vezes (iteração, indexação, comprimento), escolhe uma lista. Se iterares uma vez e o volume de dados for grande, um gerador poupará memória. Uma lista de um milhão de inteiros ocupa cerca de 8 MB, um gerador para o mesmo intervalo ocupa menos de um kilobyte. Mas um gerador não pode ser «rebobinado» nem acedido por índice. Regra prática: dados de um ficheiro ou base de dados, gerador; resultado para uso repetido,
list().
As lambdas prejudicam a legibilidade do código?
Em pequenas doses e para lógica trivial, não.
key=lambda x: x["price"]lê-se instantaneamente. Mas assim que uma lambda deixar de ser «um pensamento», extrai-a para uma função normal. As equipas com revisão de código limitam as lambdas a uma expressão sem aninhamento. Se um colega precisar de mais de cinco segundos para a compreender, reescreve-a como umdef. O nome serve como documentação:def by_price(item): return item["price"]é mais claro do que umalambdaanónima.
Quão mais lento é o Python funcional em comparação com o procedural?
Para a maioria das tarefas do dia a dia, a diferença é negligenciável, frações de microssegundo. A compreensão de lista é frequentemente mais rápida do que um ciclo equivalente porque é implementada em C. O gargalo nos programas Python não é a escolha entre
mape um ciclo, mas a entrada/saída, os pedidos de rede e a arquitetura em geral. Escreve com clareza, traça os pontos críticos comcProfilee otimiza apenas o que realmente torna as coisas lentas.
O que usar no teu código hoje
As ferramentas funcionais do Python não são dogma, mas alavancas. Não substituem o código procedural e orientado a objetos, mas complementam-no em pontos específicos.
Começa com as compreensões de lista, esta é a aplicação mais comum e segura. Domina os geradores: assim que vires uma lista temporária que é usada uma vez, pergunta-te, não deveria isto ser substituído por yield? Usa map e filter onde eles genuinamente simplificam uma cadeia de transformação. Guarda as lambdas para key= em ordenações e callbacks triviais.
O estilo funcional em Python não tem a ver com «pureza», mas com expressividade. Se depois de substituíres um ciclo por uma compreensão de lista o código ficou mais claro, fizeste tudo bem. Tenta reescrever um método problemático numa abordagem funcional e compara o diff: muitas vezes o resultado fala por si.



