Programação funcional
Nesta aula, você aprenderá os fundamentos da programação funcional em Python, incluindo funções puras, imutabilidade e o uso de map, filter e reduce. Também exploraremos o módulo functools e suas ferramentas avançadas, com exemplos práticos e exercícios para consolidar o conhecimento.
A programação funcional é um paradigma que trata a computação como a avaliação de funções matemáticas e evita mudanças de estado e dados mutáveis. Em Python, embora não seja uma linguagem puramente funcional, podemos aplicar muitos de seus princípios para escrever código mais previsível, testável e conciso. Nesta aula, vamos explorar os conceitos centrais desse paradigma e como utilizá-los no dia a dia.
Dominar a programação funcional em Python não apenas melhora a qualidade do seu código, mas também o prepara para entender bibliotecas e frameworks que adotam esses conceitos, como o Pandas e o PySpark. Além disso, muitas das funções que você já usa, como map e filter, são heranças desse paradigma.
map, filter, reduce
As funções map, filter e reduce são pilares da programação funcional. Elas permitem operar sobre coleções de dados de forma declarativa, sem a necessidade de loops explícitos e mutação de estado.
map aplica uma função a cada item de um iterável, retornando um novo iterável com os resultados. É uma forma elegante de transformar dados. Por exemplo, se quisermos elevar ao quadrado cada número de uma lista, podemos usar map com uma função lambda.
numeros = [1, 2, 3, 4]
quadrados = list(map(lambda x: x**2, numeros))
print(quadrados) # [1, 4, 9, 16]filter seleciona os elementos de um iterável que satisfazem uma condição (função que retorna True/False). Ele retorna um iterável apenas com os elementos que passam no teste. Por exemplo, para filtrar números pares:
numeros = [1, 2, 3, 4, 5, 6]
pares = list(filter(lambda x: x % 2 == 0, numeros))
print(pares) # [2, 4, 6]reduce é um pouco diferente: ela acumula os elementos de um iterável em um único valor, aplicando uma função de dois argumentos cumulativamente. Por exemplo, para somar todos os números de uma lista:
from functools import reduce
numeros = [1, 2, 3, 4]
soma = reduce(lambda a, b: a + b, numeros)
print(soma) # 10Note que reduce não é uma função embutida em Python 3, ela está no módulo functools. Isso porque, na maioria dos casos, usar um loop ou a função sum() é mais legível. No entanto, reduce é útil para operações mais complexas de acumulação.
functools
O módulo functools fornece funções de ordem superior que interagem com outras funções. As mais conhecidas são reduce, partial, lru_cache e wraps.
partial permite fixar alguns argumentos de uma função, criando uma nova função com menos parâmetros. Isso é útil para reutilizar uma função com configurações pré-definidas. Por exemplo, podemos criar uma função que sempre multiplica por 2:
from functools import partial
def multiplicar(a, b):
return a * b
dobro = partial(multiplicar, 2)
print(dobro(5)) # 10lru_cache é um decorador que armazena em cache os resultados de uma função, evitando recalcular valores para os mesmos argumentos. Isso é especialmente útil em funções recursivas ou computacionalmente caras. Exemplo com a sequência de Fibonacci:
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(fib(30)) # 832040 (rápido graças ao cache)wraps é usado ao criar decoradores para preservar os metadados da função original (como nome e docstring). Sem ele, a função decorada perde essas informações. Exemplo:
from functools import wraps
def meu_decorador(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
print("Antes")
resultado = funcao(*args, **kwargs)
print("Depois")
return resultado
return wrapper
@meu_decorador
def ola():
"""Diz olá."""
print("Olá!")
print(ola.__name__) # ola
print(ola.__doc__) # Diz olá.Imutabilidade
Imutabilidade significa que, uma vez criado, um objeto não pode ser modificado. Em programação funcional, isso é crucial, pois evita efeitos colaterais e torna o código mais previsível. Em Python, alguns tipos são imutáveis por natureza: números, strings, tuplas, frozensets. Já listas, dicionários e conjuntos são mutáveis.
Ao adotar a imutabilidade, você pode compartilhar dados com segurança entre funções sem medo de que sejam alterados inesperadamente. Por exemplo, se você passa uma lista para uma função e ela a modifica, isso pode causar bugs difíceis de rastrear. Em vez disso, prefira criar novas estruturas de dados quando precisar de alterações.
# Mutável (mau exemplo em programação funcional)
def adicionar_item(lista, item):
lista.append(item)
return lista
minha_lista = [1, 2]
print(adicionar_item(minha_lista, 3)) # [1, 2, 3]
print(minha_lista) # [1, 2, 3] (modificada!)
# Imutável (bom exemplo)
def adicionar_item_imutavel(lista, item):
nova_lista = lista + [item]
return nova_lista
minha_lista = [1, 2]
print(adicionar_item_imutavel(minha_lista, 3)) # [1, 2, 3]
print(minha_lista) # [1, 2] (original intacta)No segundo exemplo, a função não altera a lista original, mas cria uma nova. Isso é mais seguro e alinhado com os princípios funcionais.
Funções puras
Uma função pura é aquela que, dado o mesmo conjunto de entradas, sempre retorna o mesmo resultado e não causa efeitos colaterais observáveis (como modificar variáveis globais, imprimir, ou alterar argumentos). Funções puras são fáceis de testar, raciocinar e paralelizar.
Por exemplo, a função soma abaixo é pura:
def soma(a, b):
return a + bJá a função contador não é pura, pois depende de um estado externo mutável:
contador_global = 0
def incrementa():
global contador_global
contador_global += 1
return contador_globalPara tornar o código mais funcional, devemos evitar variáveis globais e modificar argumentos. Em vez disso, podemos retornar novos valores. Por exemplo, para simular um contador, poderíamos usar uma função que retorna uma tupla com o novo valor:
def incrementa(valor):
return valor + 1Assim, o estado é passado explicitamente, tornando a função pura.
Boas práticas e observações finais
Ao adotar a programação funcional em Python, tenha em mente que nem sempre é a melhor escolha. Python é uma linguagem multiparadigma, e o estilo imperativo muitas vezes é mais legível. Use os conceitos funcionais quando eles simplificarem o código, como em transformações de dados com map, filter e reduce.
Além disso, prefira funções puras sempre que possível, pois elas facilitam testes e depuração. A imutabilidade, embora nem sempre prática, pode ser aplicada em partes críticas do sistema. Lembre-se de que functools oferece ferramentas poderosas, mas use-as com moderação para não obscurecer a lógica.
Referências
- Documentação oficial de Programação Funcional em Python
- Módulo functools na documentação oficial
- Função map na documentação oficial
- Função filter na documentação oficial
- Tutorial de Programação Funcional em Python no Real Python
- Artigo sobre Programação Funcional no GeeksforGeeks
Exercícios
- Use map para converter uma lista de temperaturas em Celsius para Fahrenheit. Dada uma lista
celsius = [0, 20, 37, 100], crie uma nova lista com as temperaturas em Fahrenheit usandomap. A fórmula éF = C * 9/5 + 32.✓ Resposta:celsius = [0, 20, 37, 100] fahrenheit = list(map(lambda c: c * 9/5 + 32, celsius)) print(fahrenheit) # [32.0, 68.0, 98.6, 212.0] - Filtre os números primos de uma lista. Dada uma lista
numeros = list(range(2, 30)), usefilterpara obter apenas os números primos. Você pode criar uma função auxiliar para testar a primalidade.✓ Resposta:def eh_primo(n): if n < 2: return False for i in range(2, int(n**0.5) + 1): if n % i == 0: return False return True numeros = list(range(2, 30)) primos = list(filter(eh_primo, numeros)) print(primos) # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] - Use reduce para calcular o fatorial de um número. Dado um número
n, calculen!usandoreduceerange.✓ Resposta:from functools import reduce def fatorial(n): return reduce(lambda a, b: a * b, range(1, n+1), 1) print(fatorial(5)) # 120 - Crie uma função parcial com partial. Defina uma função
potencia(base, expoente)e usepartialpara criar uma funçãoquadradoque calcula o quadrado de um número.✓ Resposta:from functools import partial def potencia(base, expoente): return base ** expoente quadrado = partial(potencia, expoente=2) print(quadrado(5)) # 25 - Implemente uma função pura para calcular a média de uma lista. A função deve receber uma lista e retornar a média, sem modificar a lista e sem usar variáveis globais.✓ Resposta:
def media(lista): if not lista: return 0 return sum(lista) / len(lista) print(media([1, 2, 3, 4])) # 2.5