O módulo functools da biblioteca padrão do Python é um verdadeiro canivete suíço para quem escreve código profissional. Ele oferece ferramentas que otimizam performance, reduzem duplicação e tornam seu código mais expressivo — tudo isso usando apenas recursos nativos da linguagem.

Neste guia completo, você vai aprender desde os fundamentos até técnicas avançadas do módulo functools do Python, com exemplos práticos que pode aplicar imediatamente em seus projetos do dia a dia.

O Que é o Módulo functools?

O functools é um módulo da biblioteca padrão do Python projetado para funções de ordem superior e operações com callable objects. A documentação oficial do functools define seu propósito como "funções de ordem superior que atuam em ou retornam outras funções". Em termos práticos, ele fornece decorators e utilitários que tornam seu código mais rápido, limpo e reutilizável.

Se você já trabalha com Python há algum tempo, provavelmente já usou functools sem perceber — especialmente se já criou decorators com @wraps ou aplicou cache com @lru_cache. O módulo é tão útil que está presente na maioria dos projetos Python em produção.

1. @functools.lru_cache — Memoização Automática

Um dos usos mais populares do módulo é o decorator @lru_cache (Least Recently Used cache). Ele implementa memoização automática: armazena os resultados de chamadas de função e os reutiliza quando a mesma entrada aparece novamente.

from functools import lru_cache

@lru_cache(maxsize=128) def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50)) # Resultado: 12586269025

Sem @lru_cache, esta função recursiva teria complexidade exponencial. Com o cache, cada valor é calculado apenas uma vez, reduzindo a complexidade para O(n). O parâmetro maxsize define quantos resultados o cache pode armazenar — use maxsize=None para cache ilimitado (mas fique atento ao consumo de memória).

Para funções que não precisam de limite de cache, o Python 3.9 introduziu @functools.cache, que é simplesmente @lru_cache(maxsize=None) com uma sintaxe mais limpa:

from functools import cache

@cache def calcular_dados_complexos(id):

Simula processamento pesado

return sum(i * i for i in range(id * 10000))

A documentação oficial do lru_cache explica que os argumentos da função precisam ser hashable para que o cache funcione corretamente. Isso significa que você não pode usar listas, dicionários ou sets diretamente como argumentos de uma função decorada com @lru_cache.

Quando Usar @lru_cache?

  • Funções recursivas com overlapping subproblems (como Fibonacci, torre de Hanói)
  • Cálculos matemáticos caros que se repetem com mesmas entradas
  • Consultas a bancos de dados ou APIs que retornam dados relativamente estáveis
  • Processamento de arquivos de configuração ou parsing repetitivo

Cuidados ao Usar Cache

O cache do functools é armazenado em memória. Se sua função processa milhões de entradas únicas, o cache pode consumir gigabytes de RAM. Além disso, funções com efeitos colaterais (como escrever em arquivos ou enviar emails) não devem ser cacheadas, pois o resultado será o mesmo da primeira chamada.

Para funções que dependem de tempo ou estado global, o cache pode retornar dados desatualizados. Nestes casos, você pode usar lru_cache com um maxsize pequeno e limpar o cache com funcao.cache_clear() periodicamente.

2. @functools.wraps — Preservando Metadados

Quando você cria decorators em Python, um problema comum é perder os metadados da função original. O @functools.wraps resolve isso copiando atributos como __name__, __doc__ e __module__ da função original para a função wrapper.

from functools import wraps

def log_chamada(func): @wraps(func) def wrapper(*args, *kwargs): print(f"Chamando {func.name}") resultado = func(args, **kwargs) print(f"{func.name} retornou {resultado}") return resultado return wrapper

@log_chamada def somar(a, b): """Soma dois números""" return a + b

print(somar.name) # 'somar' (não 'wrapper') print(somar.doc) # 'Soma dois números' (não None)

Sem @wraps, a introspecção da função se perde — somar.__name__ retornaria 'wrapper'. Isso quebra ferramentas que dependem de metadados, como debuggers, sistemas de documentação e frameworks web. A documentação oficial do wraps recomenda seu uso em todo decorator personalizado.

Para um mergulho mais profundo em como criar e usar decorators, confira nosso guia completo sobre decorators em Python, que cria desde decorators simples até decorators aninhados com parâmetros.

3. functools.partial — Funções Parciais

O functools.partial permite "fixar" argumentos de uma função, criando uma nova função com menos parâmetros. É útil quando você precisa passar uma função como callback ou adaptar interfaces.

from functools import partial

def potencia(base, expoente): return base ** expoente

Fixa o expoente em 2

quadrado = partial(potencia, expoente=2) cubo = partial(potencia, expoente=3)

print(quadrado(5)) # 25 print(cubo(5)) # 125

Casos de uso comuns incluem:

  • Adaptar funções para APIs que exigem callbacks com assinatura específica
  • Criar funções especializadas a partir de funções genéricas
  • Reduzir boilerplate em código repetitivo com parâmetros fixos

A documentação oficial do partial mostra que você também pode usar partial para criar funções com argumentos pré-definidos para sorting, filtering e mapeamento.

from functools import partial

dados = [ {"nome": "Ana", "idade": 30}, {"nome": "Carlos", "idade": 25}, {"nome": "Beatriz", "idade": 35}, ]

ordenar_por = partial(sorted, key=lambda x: x["idade"]) print(ordenar_por(dados))

4. @functools.singledispatch — Dispatch por Tipo

Python não suporta sobrecarga de métodos como Java ou C++. Mas o @singledispatch oferece uma alternativa elegante: criar funções genéricas que se comportam de forma diferente baseado no tipo do primeiro argumento.

from functools import singledispatch

@singledispatch def processar(valor): raise TypeError(f"Tipo não suportado: {type(valor)}")

@processar.register(int) def processar_int(valor): return valor * 2

@processar.register(str) def processar_str(valor): return valor.upper()

@processar.register(list) def processar_lista(valor): return [processar(item) for item in valor]

print(processar(10)) # 20 print(processar("teste")) # TESTE print(processar([1, "a"])) # [2, "A"]

Isso é especialmente útil em sistemas de processamento de dados, serialização e validação, onde você precisa lidar com múltiplos tipos sem encher o código de if isinstance(). A documentação oficial do singledispatch detalha como criar hierarquias de registro para subtipos.

O singledispatch respeita a hierarquia de herança: se você registrar um handler para float e chamar a função com int (que é subclasse de float), o handler de float será usado como fallback.

5. @functools.total_ordering — Ordenação Automática

Implementar todos os métodos de comparação (__lt__, __le__, __gt__, __ge__, __eq__, __ne__) é tedioso e repetitivo. O decorator @total_ordering preenche os métodos que faltam a partir de __eq__ e __lt__ (ou __gt__).

from functools import total_ordering

@total_ordering class Pessoa: def init(self, nome, idade): self.nome = nome self.idade = idade

def __eq__(self, outra):
    return self.idade == outra.idade

def __lt__(self, outra):
    return self.idade < outra.idade

p1 = Pessoa("Ana", 30) p2 = Pessoa("Carlos", 25) p3 = Pessoa("Beatriz", 30)

print(p1 > p2) # True print(p1 >= p3) # True print(p2 < p1) # True

A documentação oficial do total_ordering alerta que o decorator adiciona uma pequena sobrecarga computacional, pois gera os métodos automaticamente. Para classes com muitos objetos, implementar os métodos manualmente pode ser mais eficiente.

6. functools.reduce — Redução Funcional

O reduce aplica uma função cumulativa a uma sequência, reduzindo-a a um único valor. Embora menos usado desde a ênfase do Python em loops explícitos e list comprehensions, reduce ainda é valioso em pipelines de processamento funcional.

from functools import reduce
from operator import mul

numeros = [1, 2, 3, 4, 5] produto = reduce(mul, numeros) print(produto) # 120

Equivalente com lambda

soma_total = reduce(lambda x, y: x + y, numeros) print(soma_total) # 15

Casos de uso onde reduce brilha incluem:

  • Calcular produto de sequências numéricas
  • Combinar dicionários recursivamente
  • Processar árvores de dados com funções de agregação
  • Encadear transformações funcionais

Se você está começando com Python, recomendamos nosso guia completo para iniciantes em Python, que cobre os fundamentos da linguagem antes de mergulhar em tópicos avançados como functools.

7. @functools.cached_property — Propriedades com Cache

Introduzido no Python 3.8, @cached_property transforma um método de classe em uma propriedade cujo valor é calculado apenas uma vez e cacheado durante o ciclo de vida da instância.

from functools import cached_property

class AnalisadorDados: def init(self, dados): self.dados = dados

@cached_property
def medias_por_categoria(self):
    print("Calculando médias...")
    # Simula processamento pesado
    return {cat: sum(vals) / len(vals)
            for cat, vals in self.dados.items()}

@cached_property
def total_registros(self):
    return sum(len(v) for v in self.dados.values())

analisador = AnalisadorDados({"A": [10, 20, 30], "B": [5, 15, 25]}) print(analisador.medias_por_categoria) # Calcula print(analisador.medias_por_categoria) # Cache (sem "Calculando") print(analisador.total_registros)

Diferente de @property comum, que recalcula o valor a cada acesso, @cached_property armazena o resultado em self.__dict__ após o primeiro cálculo. Isso é ideal para propriedades computacionalmente caras que não mudam durante a vida do objeto.

8. Comparação: functools.cache vs lru_cache vs cached_property

O módulo oferece três formas de cache, cada uma com seu propósito:

Recurso Escopo Limpeza Manual maxsize
@cache Chamadas de função funcao.cache_clear() Ilimitado
@lru_cache Chamadas de função funcao.cache_clear() Configurável
@cached_property Instância de classe del obj.atributo N/A

Use @cache para memoização simples sem preocupação com memória. Use @lru_cache quando precisar limitar o tamanho do cache. Use @cached_property para atributos derivados caros dentro de classes.

9. functools.update_wrapper — Versão Programática do wraps

Enquanto @wraps é a forma decorator, update_wrapper é sua versão funcional. Útil quando você precisa criar wrappers dinamicamente, fora da sintaxe de decorator.

from functools import update_wrapper

def criar_wrapper(func): def wrapper(*args, *kwargs): print(f"Antes de {func.name}") return func(args, **kwargs) return update_wrapper(wrapper, func)

def saudacao(nome): """Diz olá para alguém""" return f"Olá, {nome}!"

wrapper = criar_wrapper(saudacao) print(wrapper.name) # 'saudacao' print(wrapper.doc) # 'Diz olá para alguém'

Boas Práticas com functools

Agora que você conhece as principais ferramentas do módulo, aqui estão algumas recomendações para usar functools de forma eficiente em projetos reais:

Sempre Use @wraps em Decorators

Nunca crie um decorator sem @functools.wraps. A perda de metadados pode causar bugs silenciosos em ferramentas de logging, profiling e documentação automática.

Considere o Custo do Cache

Cache não é mágica. Avalie se o custo de armazenar resultados compensa a economia computacional. Funções chamadas com pouca repetição de argumentos se beneficiam pouco do cache.

Prefira @cache ao Invés de @lru_cache(maxsize=None)

Se você usa Python 3.9+, @functools.cache é semanticamente idêntico e mais legível que @lru_cache(maxsize=None).

Combine functools com Outros Módulos

O functools trabalha bem com itertools para pipelines de processamento funcional, com operator para operações mais limpas e com typing para código type-safe.

Exemplo Prático: Pipeline de Processamento

Vamos combinar várias ferramentas do functools em um exemplo real de processamento de dados:

from functools import reduce, partial, singledispatch, lru_cache
from operator import add

@singledispatch def transformar(valor): raise TypeError(f"Tipo não suportado: {type(valor)}")

@transformar.register(int) @lru_cache(maxsize=256) def transformar_int(valor): return valor ** 2

@transformar.register(str) def transformar_str(valor): return valor.strip().lower()

Pipeline de processamento

pipeline = partial(reduce, lambda acc, x: acc + transformar(x))

numeros = [1, 2, 3, 4, 5] textos = [" Python ", " FUNCTOOLS ", " módulo "]

print(pipeline(numeros, 0)) # 1 + 4 + 9 + 16 + 25 = 55 print(pipeline(textos, "")) # "python functools módulo"

Este exemplo demonstra como singledispatch, lru_cache, partial e reduce podem ser combinados para criar pipelines de processamento elegantes e eficientes.

Conclusão

O módulo functools é uma das joias escondidas da biblioteca padrão do Python. Suas ferramentas — de cache a dispatch por tipo, de funções parciais a ordenação total — resolvem problemas recorrentes de forma elegante e performática.

Dominar o functools é um marco na jornada de qualquer desenvolvedor Python. Ele não apenas torna seu código mais eficiente, mas também mais expressivo e alinhado com as boas práticas da linguagem. Se você quer escrever Python profissional, o functools será um dos seus melhores aliados.

Continue explorando os módulos padrão do Python. Confira também nosso guia completo sobre decorators em Python para aprofundar seu conhecimento, e não deixe de visitar o site oficial do Python para referência completa. O PEP 257 sobre docstrings também é uma leitura recomendada para complementar seus estudos sobre metadados de funções. Para tutoriais práticos adicionais, o Real Python: functools Guide oferece uma visão complementar com exemplos do mundo real.