Funções como objetos: passando e retornando funções

1. Funções são Objetos de Primeira Classe em Python

Em Python, funções são cidadãs de primeira classe — isso significa que podem ser tratadas como qualquer outro objeto: atribuídas a variáveis, armazenadas em estruturas de dados e passadas como argumentos. Essa característica é fundamental para o paradigma funcional da linguagem.

def saudacao(nome):
    return f"Olá, {nome}!"

# Atribuindo função a uma variável
minha_funcao = saudacao
print(minha_funcao("Ana"))  # Olá, Ana!
print(minha_funcao.__name__)  # saudacao
print(id(saudacao) == id(minha_funcao))  # True

Funções podem ser armazenadas em listas e dicionários:

def somar(a, b): return a + b
def subtrair(a, b): return a - b
def multiplicar(a, b): return a * b

operacoes = {
    '+': somar,
    '-': subtrair,
    '*': multiplicar
}

print(operacoes['+'](10, 5))  # 15
print(operacoes['*'](10, 5))  # 50

2. Passando Funções como Argumentos

Passar funções como argumentos permite criar código flexível e reutilizável. O exemplo clássico é o parâmetro key de funções como sorted() e max():

nomes = ["Alice", "bob", "Charlie", "david"]
print(sorted(nomes, key=len))  # ['bob', 'Alice', 'david', 'Charlie']
print(sorted(nomes, key=str.lower))  # ['Alice', 'bob', 'Charlie', 'david']

Podemos criar nosso próprio filtro genérico:

def filtrar(lista, criterio):
    """Filtra elementos de uma lista usando uma função de teste."""
    return [item for item in lista if criterio(item)]

def maior_que_10(x): return x > 10
def par(x): return x % 2 == 0

numeros = [3, 15, 8, 22, 7, 14]
print(filtrar(numeros, maior_que_10))  # [15, 22, 14]
print(filtrar(numeros, par))  # [8, 22, 14]

3. Retornando Funções de Outras Funções (Closures)

Uma closure é uma função interna que "captura" variáveis do escopo externo, mesmo após a função externa ter terminado sua execução:

def multiplicador(n):
    """Retorna uma função que multiplica qualquer valor por n."""
    def multiplicar(x):
        return x * n
    return multiplicar

dobro = multiplicador(2)
triplo = multiplicador(3)

print(dobro(10))  # 20
print(triplo(10))  # 30
print(dobro.__closure__[0].cell_contents)  # 2

Isso permite encapsular estado de forma elegante e criar funções com comportamento parametrizado:

def contador():
    """Closure que mantém estado interno."""
    valor = 0
    def incrementar():
        nonlocal valor
        valor += 1
        return valor
    return incrementar

meu_contador = contador()
print(meu_contador())  # 1
print(meu_contador())  # 2
print(meu_contador())  # 3

4. Decorators: Aplicação Prática de Funções como Objetos

Decorators são funções que recebem outra função e retornam uma versão modificada dela. É uma aplicação direta do conceito de funções como objetos:

import time

def medir_tempo(func):
    """Decorator que mede o tempo de execução de uma função."""
    def wrapper(*args, **kwargs):
        inicio = time.time()
        resultado = func(*args, **kwargs)
        fim = time.time()
        print(f"{func.__name__} executou em {fim - inicio:.4f}s")
        return resultado
    return wrapper

@medir_tempo
def calcular_fatorial(n):
    from math import factorial
    return factorial(n)

print(calcular_fatorial(100))

Decorators com argumentos requerem um nível extra de aninhamento:

def log_com_prefixo(prefixo):
    """Decorator com argumento para customizar o log."""
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"[{prefixo}] Executando {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log_com_prefixo("INFO")
def processar_dados():
    return "Processamento concluído"

print(processar_dados())

5. Funções de Alta Ordem da Biblioteca Padrão

Python oferece funções de alta ordem prontas na biblioteca padrão:

from functools import reduce, partial

# map: transforma cada elemento
numeros = [1, 2, 3, 4, 5]
quadrados = list(map(lambda x: x**2, numeros))
print(quadrados)  # [1, 4, 9, 16, 25]

# filter: seleciona elementos que satisfazem condição
pares = list(filter(lambda x: x % 2 == 0, numeros))
print(pares)  # [2, 4]

# reduce: reduz sequência a um único valor
soma = reduce(lambda a, b: a + b, numeros)
print(soma)  # 15

# partial: fixa argumentos de uma função
def potencia(base, expoente):
    return base ** expoente

quadrado = partial(potencia, expoente=2)
cubo = partial(potencia, expoente=3)
print(quadrado(5))  # 25
print(cubo(5))      # 125

6. Composição de Funções

Compor funções é criar novas funções a partir da combinação de outras:

def compor(f, g):
    """Retorna f(g(x))."""
    return lambda x: f(g(x))

def dobrar(x): return x * 2
def somar_5(x): return x + 5

resultado = compor(dobrar, somar_5)
print(resultado(10))  # 30 (dobrar(somar_5(10)) = 30)

# Pipe operator funcional
def pipeline(*funcoes):
    """Executa funções em sequência, passando resultado adiante."""
    def aplicar(valor):
        for func in funcoes:
            valor = func(valor)
        return valor
    return aplicar

def limpar(texto): return texto.strip().lower()
def remover_espacos(texto): return texto.replace(" ", "_")
def adicionar_prefixo(texto): return f"dados_{texto}"

processar = pipeline(limpar, remover_espacos, adicionar_prefixo)
print(processar("  Exemplo de DADOS  "))  # dados_exemplo_de_dados

7. Considerações de Performance e Boas Práticas

O uso de funções como objetos traz flexibilidade, mas é importante considerar o equilíbrio entre expressividade e performance:

from typing import Callable, List, TypeVar

T = TypeVar('T')

# Type hints melhoram legibilidade e documentação
def aplicar_transformacao(
    dados: List[T],
    transformacao: Callable[[T], T]
) -> List[T]:
    """Aplica uma transformação a cada elemento da lista."""
    return [transformacao(item) for item in dados]

# Prefira funções nomeadas para lógica complexa
def processar_cliente(cliente: dict) -> dict:
    """Processa dados de um cliente (lógica complexa)."""
    cliente['nome'] = cliente['nome'].title()
    cliente['email'] = cliente['email'].lower()
    return cliente

clientes = [
    {'nome': 'ana silva', 'email': 'ANA@exemplo.com'},
    {'nome': 'CARLOS SOUZA', 'email': 'carlos@exemplo.com'}
]

resultados = aplicar_transformacao(clientes, processar_cliente)
print(resultados)

Para operações simples em grandes volumes de dados, loops tradicionais podem ser mais rápidos que closures e funções aninhadas. Use funções como objetos onde a legibilidade e reutilização são prioridades.

Referências