Pular para o conteúdo principal

Cache Python: Dois métodos simples

Aprenda a usar decoradores como @functools.lru_cache ou @functools.cache para armazenar funções em cache no Python.
Atualizado 8 de set. de 2025  · 12 min lido

Neste artigo, vamos aprender sobre cache em Python. Vamos entender o que é e como usá-lo de forma eficaz.

O cache é uma técnica usada para melhorar o desempenho dos aplicativos, armazenando temporariamente os resultados obtidos pelo programa para reutilizá-los se necessário posteriormente.

Neste tutorial, vamos aprender diferentes técnicas para cache em Python, incluindo os decoradores @lru_cache e @cache no módulo functools.

Para quem está com pressa, vamos começar com uma implementação de cache bem curta e depois continuar com mais detalhes.

Resposta curta: Implementação de cache em Python

Pra criar um cache em Python, a gente pode usar o decorador @cache do módulo functools. No código abaixo, repara que a função print() só é executada uma vez:

import functools

@functools.cache
def square(n):
    print(f"Calculating square of {n}")
    return n * n

# Testing the cached function
print(square(4))
print(square(4))

# Calculating square of 4
# 16
# 16

O que é cache em Python?

Digamos que precisamos resolver um problema matemático e levamos uma hora pra chegar na resposta certa. Se tivéssemos que resolver o mesmo problema no dia seguinte, seria útil reutilizar nosso trabalho anterior, em vez de começar tudo de novo.

O cache em Python funciona de um jeito parecido — ele guarda valores quando eles são calculados dentro de chamadas de função pra usar de novo quando precisar. Esse tipo de cache também é conhecido como memoização.

Vamos ver um pequeno exemplo que calcula a soma de um grande intervalo de números duas vezes:

output = sum(range(100_000_001))
print(output)
output = sum(range(100_000_001))
print(output)

# 5000000050000000
# 5000000050000000

O programa precisa calcular a soma toda vez. A gente pode confirmar isso cronometrando as duas chamadas:

import timeit

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

A saída mostra que as duas chamadas levam mais ou menos o mesmo tempo (dependendo da nossa configuração, podemos ter tempos de execução mais rápidos ou mais lentos).

Mas dá pra usar um cache pra não ter que calcular o mesmo valor várias vezes. A gente pode redefinir o nome sum usando a função cache() no módulo functools que já vem instalado:

import functools
import timeit

sum = functools.cache(sum)

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

A segunda chamada agora leva alguns microssegundos em vez de mais de um segundo, porque o resultado da soma dos números de 0 a 100.000.000 já foi calculado e armazenado em cache — a segunda chamada usa o valor que foi calculado e armazenado anteriormente.

Acima, usamos o decorador functools.cache() para incluir um cache na função embutida sum(). Só pra constar, um decorador em Python é uma função que mexe no jeito que outra função funciona, sem mexer no código dela de verdade. Você pode aprender mais sobre decoradores neste Tutorial de Decoradores Python.

O decorador functools.cache() foi adicionado ao Python na versão 3.9, mas a gente pode usar functools.lru_cache() para versões mais antigas. Na próxima seção, vamos ver essas duas maneiras de criar um cache, incluindo o uso da notação decoradora mais comum, como @cache.

Cache em Python: Diferentes métodos

O módulo functools do Python tem dois decoradores para aplicar o cache às funções. Vamos dar uma olhada em functools.lru_cache() e functools.cache() com um exemplo.

Vamos escrever uma função sum_digits() que recebe uma sequência de números e devolve a soma dos dígitos desses números. Por exemplo, se usarmos a tupla (23, 43, 8) como entrada, então:

  • A soma dos dígitos de 23 é cinco.
  • A soma dos dígitos de 43 é sete.
  • A soma dos dígitos de 8 é oito.
  • Então, o total é 20.

Essa é uma maneira de escrevermos nossa função sum_digits():

def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

numbers = 23, 43, 8

print(sum_digits(numbers))

# 20

Vamos usar essa função para ver diferentes maneiras de criar um cache.

Cache manual em Python

Primeiro, vamos criar o cache manualmente. Embora também pudéssemos automatizar isso facilmente, criar um cache manualmente nos ajuda a entender o processo.

Vamos criar um dicionário e adicionar pares de chave-valor cada vez que chamarmos a função com um novo valor para guardar os resultados. Se chamarmos a função com um valor que já está guardado nesse dicionário, a função vai devolver o valor guardado sem precisar calcular de novo:

import random
import timeit

def sum_digits(numbers):
    if numbers not in sum_digits.my_cache:
        sum_digits.my_cache[numbers] = sum(
            int(digit) for number in numbers for digit in str(number)
        )
    return sum_digits.my_cache[numbers]
sum_digits.my_cache = {}

numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000))

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

A segunda chamada para sum_digits(numbers) é bem mais rápida que a primeira porque usa o valor armazenado em cache.

Vamos agora explicar o código acima com mais detalhes. Primeiro, repara que criamos o dicionário sum_digits.my_cache depois de definir a função, mesmo que a usemos na definição da função.

A função sum_digits() verifica se o argumento passado para a função já é uma das chaves no dicionário sum_digits.my_cache. A soma de todos os dígitos só é calculada se o argumento ainda não estiver no cache.

Como o argumento que usamos ao chamar a função serve como chave no dicionário, ele precisa ser um tipo de dados hashável. Uma lista não é hashable, então não dá pra usar como chave num dicionário. Por exemplo, vamos tentar substituir numbers por uma lista em vez de uma tupla — isso vai gerar um erro de tipo TypeError:

# ...

numbers = [random.randint(1, 1000) for _ in range(1_000_000)]

# ...
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'

Criar um cache manualmente é ótimo para fins de aprendizagem, mas agora vamos explorar maneiras mais rápidas de fazer isso.

Cache Python com functools.lru_cache()

O Python tem o decorador lru_cache() desde a versão 3.2. O “lru” no começo do nome da função significa “menos usado recentemente”. A gente pode pensar no cache como uma caixa pra guardar coisas que a gente usa bastante — quando ela fica cheia, a estratégia LRU joga fora o item que a gente não usa há mais tempo pra abrir espaço pra algo novo.

Vamos decorar nossa função sum_digits() com @functools.lru_cache:

import functools
import random
import timeit

@functools.lru_cache
def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000))

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

Graças ao cache, a segunda chamada leva bem menos tempo pra rodar.

Por padrão, o cache guarda os primeiros 128 valores calculados. Quando todos os 128 lugares estiverem ocupados, o algoritmo apaga o valor menos usado recentemente (LRU) para abrir espaço para novos valores.

A gente pode definir um tamanho máximo diferente para o cache quando decoramos a função usando o parâmetro maxsize:

import functools
import random
import timeit

@functools.lru_cache(maxsize=5)
def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

# ...

Nesse caso, o cache só guarda cinco valores. Também podemos definir o argumento maxsize como None se não quisermos limitar o tamanho do cache.

Cache Python com functools.cache()

O Python 3.9 tem um decorador de cache mais simples e rápido —functools.cache(). Esse decorador tem duas características principais:

  • Não tem um tamanho máximo — é tipo chamar functools.lru_cache(maxsize=None).
  • Ele guarda todas as chamadas de função e seus resultados (não usa a estratégia LRU). Isso é legal para funções com saídas relativamente pequenas ou quando a gente não precisa se preocupar com as limitações do tamanho do cache.

Vamos usar o decorador @functools.cache na função sum_digits():

import functools
import random
import timeit

@functools.cache
def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000))

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

Decorar sum_digits() com @functools.cache é o mesmo que colocar sum_digits em functools.cache():

# ...

def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

sum_digits = functools.cache(sum_digits)

Observe que também podemos usar um estilo de importação diferente:

from functools import cache

Assim, podemos decorar nossas funções usando só um @cache.

Outras estratégias de armazenamento em cache

As próprias ferramentas do Python usam a estratégia de cache LRU, onde as entradas menos usadas são apagadas pra dar espaço pra novos valores.

Vamos dar uma olhada em algumas outras estratégias de cache:

  • Primeiro a entrar, primeiro a sair (FIFO): Quando o cache fica cheio, o primeiro item adicionado é removido para abrir espaço para novos valores. A diferença entre LRU e FIFO é que o LRU mantém os itens usados recentemente no cache, enquanto o FIFO descarta o item mais antigo, independentemente do uso.
  • Último a entrar, primeiro a sair (LIFO): O item adicionado mais recentemente é removido quando o cache fica cheio. Imagina uma pilha de pratos numa cafeteria. A placa que colocamos na pilha mais recentemente (última a entrar) é a que vamos tirar primeiro (primeira a sair).
  • Mais recentemente usado (MRU): O valor que foi usado mais recentemente é descartado quando é preciso espaço no cache.
  • Substituição aleatória (RR): Essa estratégia descarta um item aleatoriamente para abrir espaço para um novo.

Essas estratégias também podem ser combinadas com medidas de tempo de vida válido — isso se refere ao tempo durante o qual um dado no cache é considerado válido ou relevante. Imagina uma notícia em um cache. Pode ser acessado com frequência (o LRU o manteria), mas depois de uma semana, a notícia pode estar desatualizada.

Cache em Python: Casos de uso comuns

Até agora, usamos exemplos simples pra facilitar o aprendizado. Mas o cache tem várias aplicações na vida real.

Na ciência de dados, a gente costuma fazer operações repetidas em grandes conjuntos de dados. Usar resultados em cache reduz o tempo e o custo de fazer os mesmos cálculos várias vezes nos mesmos conjuntos de dados.

Também podemos usar o cache para salvar recursos externos, como páginas da web ou bancos de dados. Vamos ver um exemplo e armazenar um artigo do DataCamp no cache. Mas primeiro precisamos instalar o módulo de terceiros requests executando a seguinte linha no terminal:

$ python -m pip install requests

Depois de instalar o requests, podemos tentar o seguinte código, que tenta buscar o mesmo artigo do DataCamp duas vezes usando o decorador @lru_cache:

import requests
from functools import lru_cache

@lru_cache(maxsize=10)
def get_article(url):
    print(f"Fetching article from {url}")
    response = requests.get(url)
    return response.text

print(get_article("https://www.datacamp.com/tutorial/decorators-python"))
print(get_article("https://www.datacamp.com/tutorial/decorators-python"))

Só pra constar, a gente cortou a saída porque era muito longa. Mas, repara que só a primeira chamada para get_article() mostra a frase Fetching article from {url}.

Isso porque a página da web só é acessada na primeira vez que a chamada é feita. O resultado fica guardado no cache da função. Quando a gente pede a mesma página pela segunda vez, os dados que estão guardados no cache são mostrados.

O cache garante que não haja atrasos desnecessários ao buscar os mesmos dados várias vezes. As APIs externas também costumam ter limites de taxa e custos associados à obtenção de dados. O cache reduz os custos das APIs e a chance de atingir os limites de taxa.

Outro caso de uso comum é em aplicativos de machine learning, onde vários cálculos caros precisam ser repetidos. Por exemplo, se precisarmos tokenizar e vetorizar um texto antes de usá-lo em um modelo de machine learning, podemos guardar o resultado em um cache. Assim, não precisaremos repetir as operações que exigem muito processamento.

Desafios comuns ao usar cache em Python

A gente aprendeu sobre as vantagens do cache no Python. Também tem alguns desafios e desvantagens que você precisa ter em mente ao implementar um cache:

  • Invalidação e consistência do cache: Os dados podem mudar com o tempo. Então, os valores guardados no cache também podem precisar ser atualizados ou removidos.
  • Gerenciamento de memória: Armazenar grandes quantidades de dados em um cache requer memória, e isso pode causar problemas de desempenho se o cache crescer indefinidamente.
  • Complexidade: Adicionar caches deixa o sistema mais complicado na hora de criar e manter o cache. Muitas vezes, os benefícios superam esses custos, mas essa complexidade maior pode causar erros difíceis de encontrar e corrigir.

Conclusão

Podemos usar o cache para otimizar o desempenho quando operações computacionalmente intensivas são repetidas nos mesmos dados.

Python tem dois decoradores pra criar um cache quando chamar funções: @lru_cache e @cache no módulo functools.

Precisamos garantir, no entanto, que a cache esteja sempre atualizada e que a memória seja gerenciada corretamente.

Se você quiser saber mais sobre cache e Python, dá uma olhada neste programa de seis módulos sobre programação em Python.


Stephen Gruppetta's photo
Author
Stephen Gruppetta
LinkedIn
Twitter

Estudei Física e Matemática em nível superior na Universidade de Malta. Depois, mudei-me para Londres e fiz meu doutorado em Física no Imperial College. Trabalhei em novas técnicas ópticas para obter imagens da retina humana. Agora, meu foco é escrever sobre Python, comunicar sobre Python e ensinar Python.

Tópicos

Aprenda Python para ciência de dados!

Curso

Caixa de ferramentas Python

4 h
304.7K
Continue aprimorando suas habilidades em ciência de dados aprendendo sobre iteradores e compreensões de listas.
Ver detalhesRight Arrow
Iniciar curso
Ver maisRight Arrow
Relacionado

Tutorial

Tutorial de funções Python

Um tutorial sobre funções em Python que aborda como escrever funções, como chamá-las e muito mais!
Karlijn Willems's photo

Karlijn Willems

Tutorial

Tutorial de lambda em Python

Aprenda uma maneira mais rápida de escrever funções em tempo real com as funções lambda.
DataCamp Team's photo

DataCamp Team

Tutorial

30 truques legais do Python para melhorar seu código, com exemplos

A gente selecionou 30 truques legais de Python que você pode usar pra melhorar seu código e desenvolver suas habilidades em Python.
Kurtis Pykes 's photo

Kurtis Pykes

Tutorial

Otimização em Python: Técnicas, pacotes e práticas recomendadas

Este artigo ensina a você sobre otimização numérica, destacando diferentes técnicas. Ele discute os pacotes Python, como SciPy, CVXPY e Pyomo, e fornece um notebook DataLab prático para você executar exemplos de código.
Kurtis Pykes 's photo

Kurtis Pykes

Tutorial

21 ferramentas essenciais do Python

Aprenda sobre as ferramentas Python essenciais para o desenvolvimento de software, raspagem e desenvolvimento da Web, análise e visualização de dados e aprendizado de máquina.
Abid Ali Awan's photo

Abid Ali Awan

Ver maisVer mais