Programação funcional em Python
1. Introdução à Programação Funcional no Contexto Python
A programação funcional é um paradigma que trata a computação como avaliação de funções matemáticas, evitando mudanças de estado e dados mutáveis. Python, embora seja uma linguagem multiparadigma, oferece suporte robusto a conceitos funcionais, permitindo que desenvolvedores combinem o melhor de diferentes abordagens.
Diferentemente da programação imperativa (focada em sequências de instruções que modificam estado) e da orientação a objetos (centrada em objetos que encapsulam estado e comportamento), a programação funcional enfatiza:
- Funções puras: sem efeitos colaterais
- Imutabilidade: dados que não mudam após criação
- Composição: construção de soluções complexas a partir de funções simples
Em Python, funções são cidadãos de primeira classe: podem ser atribuídas a variáveis, passadas como argumentos e retornadas de outras funções. Isso é a base para todo o paradigma funcional na linguagem.
# Função como cidadão de primeira classe
def saudacao(nome):
return f"Olá, {nome}!"
minha_funcao = saudacao
print(minha_funcao("Maria")) # Olá, Maria!
# Função como argumento
def executar(f, valor):
return f(valor)
print(executar(saudacao, "João")) # Olá, João!
2. Funções Puras e Imutabilidade
Uma função pura é determinística (mesma entrada → mesma saída) e não produz efeitos colaterais (não modifica variáveis externas, não faz I/O, não altera argumentos).
# Função pura - determinística e sem efeitos colaterais
def somar(a, b):
return a + b
# Função impura - depende de estado externo
total = 0
def adicionar_ao_total(valor):
global total
total += valor
return total
Para manter a imutabilidade, Python oferece estruturas como tuple, frozenset e str. Ao trabalhar com listas e dicionários, prefira criar cópias:
from copy import deepcopy
# Evitando mutação
def adicionar_item(lista_original, item):
"""Retorna nova lista sem modificar a original"""
return lista_original + [item] # Cria nova lista
dados = [1, 2, 3]
novos_dados = adicionar_item(dados, 4)
print(dados) # [1, 2, 3] - original intacta
print(novos_dados) # [1, 2, 3, 4]
# deepcopy para estruturas aninhadas
config = {"nivel": 1, "opcoes": {"som": True}}
config_copia = deepcopy(config)
config_copia["opcoes"]["som"] = False
print(config["opcoes"]["som"]) # True - original preservada
3. Funções de Alta Ordem: map, filter e reduce
map()
Aplica uma função a cada elemento de um iterável, retornando um iterador.
numeros = [1, 2, 3, 4, 5]
# Com map
quadrados = list(map(lambda x: x ** 2, numeros))
print(quadrados) # [1, 4, 9, 16, 25]
# Equivalente com list comprehension
quadrados_lc = [x ** 2 for x in numeros]
# Múltiplos iteráveis
def somar(a, b):
return a + b
resultado = list(map(somar, [1, 2, 3], [10, 20, 30]))
print(resultado) # [11, 22, 33]
filter()
Seleciona elementos que satisfazem uma condição.
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pares = list(filter(lambda x: x % 2 == 0, numeros))
print(pares) # [2, 4, 6, 8, 10]
# Com função nomeada
def maior_que_cinco(x):
return x > 5
selecionados = list(filter(maior_que_cinco, numeros))
print(selecionados) # [6, 7, 8, 9, 10]
reduce()
Do módulo functools, acumula sequencialmente os elementos.
from functools import reduce
numeros = [1, 2, 3, 4, 5]
# Soma acumulada
soma = reduce(lambda a, b: a + b, numeros)
print(soma) # 15
# Produto acumulado
produto = reduce(lambda a, b: a * b, numeros)
print(produto) # 120
# Com valor inicial
soma_inicial = reduce(lambda a, b: a + b, numeros, 100)
print(soma_inicial) # 115
4. Expressões Lambda e Funções Anônimas
Lambda são funções anônimas de uma única expressão, ideais para operações simples.
# Sintaxe: lambda argumentos: expressão
dobro = lambda x: x * 2
print(dobro(5)) # 10
# Uso com sorted
pessoas = [("Ana", 25), ("João", 30), ("Maria", 20)]
ordenado = sorted(pessoas, key=lambda pessoa: pessoa[1])
print(ordenado) # [('Maria', 20), ('Ana', 25), ('João', 30)]
# Evite lambda para lógica complexa
# Ruim:
calcular = lambda x, y: x ** 2 + y * 3 - (x + y) ** 0.5
# Melhor:
def calcular(x, y):
return x ** 2 + y * 3 - (x + y) ** 0.5
5. Ferramentas do Módulo itertools
O módulo itertools fornece iteradores eficientes para composição funcional.
from itertools import count, cycle, repeat, product, combinations, groupby, islice, chain
# Iteradores infinitos
contador = count(start=10, step=2)
print(list(islice(contador, 5))) # [10, 12, 14, 16, 18]
# Combinatória
letras = ['A', 'B', 'C']
combinacoes = list(combinations(letras, 2))
print(combinacoes) # [('A', 'B'), ('A', 'C'), ('B', 'C')]
# Pipeline funcional com itertools
dados = [1, 2, 2, 3, 3, 3, 4, 5, 5]
agrupado = groupby(sorted(dados))
resultado = {k: len(list(v)) for k, v in agrupado}
print(resultado) # {1: 1, 2: 2, 3: 3, 4: 1, 5: 2}
# chain para concatenar iteráveis
lista1 = [1, 2, 3]
lista2 = [4, 5, 6]
concatenado = list(chain(lista1, lista2))
print(concatenado) # [1, 2, 3, 4, 5, 6]
6. functools: Decoradores e Utilitários Funcionais
from functools import partial, lru_cache, singledispatch, reduce
# partial - fixa argumentos
def potencia(base, expoente):
return base ** expoente
quadrado = partial(potencia, expoente=2)
cubo = partial(potencia, expoente=3)
print(quadrado(5)) # 25
print(cubo(3)) # 27
# lru_cache - memoização automática
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100)) # 354224848179261915075 (rápido!)
# singledispatch - despacho polimórfico
@singledispatch
def processar(valor):
raise NotImplementedError(f"Tipo não suportado: {type(valor)}")
@processar.register(int)
def _(valor):
return f"Inteiro: {valor * 2}"
@processar.register(str)
def _(valor):
return f"String: {valor.upper()}"
print(processar(5)) # Inteiro: 10
print(processar("ola")) # String: OLA
# Composição de funções (implementação caseira)
def compor(*funcoes):
def aplicar(x):
resultado = x
for f in reversed(funcoes):
resultado = f(resultado)
return resultado
return aplicar
dobrar = lambda x: x * 2
incrementar = lambda x: x + 1
funcao_composta = compor(dobrar, incrementar)
print(funcao_composta(5)) # (5 + 1) * 2 = 12
7. Generators e Lazy Evaluation
Generators permitem avaliação preguiçosa, processando dados sob demanda.
# Função geradora
def fibonacci_ate(n):
a, b = 0, 1
while a < n:
yield a
a, b = b, a + b
for num in fibonacci_ate(100):
print(num, end=" ") # 0 1 1 2 3 5 8 13 21 34 55 89
# Expressão geradora vs list comprehension
numeros = range(1000000)
# List comprehension - consome memória imediatamente
quadrados_lista = [x**2 for x in numeros] # ~8MB em memória
# Expressão geradora - sob demanda
quadrados_gen = (x**2 for x in numeros) # ~56 bytes
# Pipeline lazy com itertools
from itertools import islice, takewhile
def processar_stream(dados):
pipeline = (x ** 2 for x in dados)
pipeline = (x for x in pipeline if x % 2 == 0)
pipeline = (x / 2 for x in pipeline)
return pipeline
stream = processar_stream(range(100))
resultados = list(islice(stream, 5))
print(resultados) # [0, 2, 8, 18, 32]
8. Casos de Uso Práticos e Boas Práticas
Exemplo de ETL funcional
from functools import reduce
from itertools import groupby
# Dados brutos
transacoes = [
{"id": 1, "categoria": "alimentacao", "valor": 50.0, "data": "2024-01-01"},
{"id": 2, "categoria": "transporte", "valor": 30.0, "data": "2024-01-01"},
{"id": 3, "categoria": "alimentacao", "valor": 80.0, "data": "2024-01-02"},
{"id": 4, "categoria": "lazer", "valor": 100.0, "data": "2024-01-02"},
]
# Pipeline funcional
def extrair_validos(dados):
return filter(lambda t: t["valor"] > 0, dados)
def agrupar_por_categoria(dados):
sorted_data = sorted(dados, key=lambda t: t["categoria"])
return groupby(sorted_data, key=lambda t: t["categoria"])
def somar_por_grupo(grupos):
return {
cat: reduce(lambda acc, t: acc + t["valor"], trans, 0)
for cat, trans in grupos
}
# Execução do pipeline
resultado = somar_por_grupo(agrupar_por_categoria(extrair_validos(transacoes)))
print(resultado) # {'alimentacao': 130.0, 'lazer': 100.0, 'transporte': 30.0}
Boas práticas
- Use programação funcional para transformações de dados claras e previsíveis
- Evite overengineering: nem tudo precisa ser funcional
- Misture paradigmas: use classes para estado complexo e funções puras para lógica de negócio
- Considere performance: lambdas e funções têm overhead; para loops críticos, list comprehensions ou loops tradicionais podem ser mais rápidos
# Exemplo de mistura de paradigmas
class ProcessadorDados:
def __init__(self, dados):
self.dados = dados # Estado encapsulado
def transformar(self, funcao):
# Função pura para transformação
return list(map(funcao, self.dados))
def filtrar(self, condicao):
return list(filter(condicao, self.dados))
processador = ProcessadorDados([1, 2, 3, 4, 5])
resultado = processador.transformar(lambda x: x * 2)
print(resultado) # [2, 4, 6, 8, 10]
Referências
- Documentação oficial: itertools — Referência completa do módulo itertools com exemplos de todos os iteradores
- Documentação oficial: functools — Documentação do módulo functools incluindo partial, lru_cache e singledispatch
- Python Functional Programming HOWTO — Guia oficial da documentação Python sobre programação funcional
- Real Python: Functional Programming in Python — Tutorial prático cobrindo map, filter, reduce e lambda expressions
- GeeksforGeeks: Functional Programming in Python — Artigo abrangente com exemplos de funções puras, imutabilidade e generators
- Programiz: Python Functional Programming — Tutorial didático com exemplos passo a passo de conceitos funcionais em Python