Utilizar um "cache de função" significa armazenar os resultados das chamadas de uma função, evitando reprocessamentos demorados para os mesmos argumentos.
A ideia é armazenar os argumentos (parâmetros) recebidos pela função e o resultado do processamento retornado pela função.
Assim, quando a função recebe novamente os mesmos argumentos, em vez de executar seu processamento para encontrar o resultado, apenas retorna o resultado previamente armazenado.
Vamos a um exemplo, para melhorar o entendimento.
Digamos que temos uma função que calcula a multiplicação de um número inteiro pelo seu sucessor:def vezes_proximo(numero):
print('número =', numero)
proximo = numero + 1
print('próximo =', proximo)
resultado = numero * proximo
print('resultado =', resultado)
return resultado
Ao executarmos essa função pela primeira vez, imprimindo seu retorno:
print(vezes_proximo(2))
Obteremos as seguintes informações:
número = 2
próximo = 3
resultado = 6
6
E sempre que executarmos novamente exatamente o mesmo comando, obteremos o mesmo resultado, como esperado!
print(vezes_proximo(2))
número = 2
próximo = 3
resultado = 6
6
Porém, como evitar esse reprocessamento quando não desejado?
É justamente para isso que utilizamos um cache de função.
Implementação
Vou mostrar uma implementação de cache de função bem simples, com intuito meramente didático.
Podemos criar uma variável e uma função para nos ajudar a atingir o objetivo.
- Uma variável, do tipo dicionário, para armazenar os resultados já calculados;
- Uma função que chama a função que queremos fazer cache, no caso a "vezes_proximo", apenas quando necessário.
resultados_anteriores = {}
def cached_vezes_proximo(numero):
if numero not in resultados_anteriores:
resultados_anteriores[numero] = vezes_proximo(numero)
return resultados_anteriores[numero]
O que acontece ao ser chamada a função "cached_vezes_proximo":
- A função verifica se a argumento "numero" já está guardado como chave do dicionário "resultados_anteriores";
- Se não está guardado, chama a função "vezes_proximo" e guarda o resultado no dicionário "resultados_anteriores";
- Finalmente, retorna o resultado guardado em "resultados_anteriores", independentemente de ter sido calculado na hora ou anteriormente.
Assim, ao executarmos:
print(cached_vezes_proximo(2))
Obteremos as seguintes informações no terminal:
número = 2
próximo = 3
resultado = 6
6
Porém, ao executarmos novamente, obteremos apenas a seguinte informação no terminal:
6
Ou seja, na segunda execução não houve chamada à função "vezes_proximo". Apenas ocorreu o retorno do resultado que já tinha sido calculado na primeira chamada.
Quando utilizar
O cache de função é muito útil para economizar tempo de execução e poupar recursos computacionais custosos. Deve ser utilizado quando uma função:
- Retorna sempre o mesmo resultado quando chamada com os mesmo argumentos;
- Tem execução custosa, seja por processar muito internamente ou por utilizar recursos externos demorados;
- É chamada muitas vezes com o mesmo argumento em um espaço de tempo relativamente curto.
Exemplo simples:
- Sistema de Sugestões de Produtos Baseadas em Recomendações
- Cenário: Um sistema de vendas com uma função que recebe o código do cliente e sugere produtos com base em algoritmos de recomendação.
- Uso de Cache: Como os cálculos de recomendação podem ser complexos, os resultados podem ser armazenados em cache por um tempo para cada cliente, proporcionando sugestões rápidas sempre que o cliente recarregar uma página do sistema.
Neste exemplo, você pode ter percebido um detalhe não abordado no código: "resultados podem ser armazenados... por um tempo". Esse "detalhe" é muito importante. Falei em armazenar, mas armazenar quanto e por quanto tempo? Isso é uma discussão longa que poderemos tratar em outro momento.
Quando não utilizar
O cache de função não deve ser utilizado quando uma função:
- Não retorna sempre o mesmo valor para os mesmos argumentos;
- Não é chamada muitas vezes com os mesmos argumentos;
- É muito simples, sendo mais leve reexecutar o cálculo do que controlar o cache.
Na prática
A solução de cache desenvolvida como exemplo neste artigo é trabalhosa e tem poucos recursos. Não cuida, por exemplo, do tamanho do cache de armazenamento e do ciclo de vida do dado armazenado.
Existem várias formas de implementar um cache em uma função no Python. Porém, abordarei apenas uma das mais comuns: utilizar o decorador "lru_cache", do módulo "functools", que está disponível por padrão no Python.
Em vez de criarmos uma variável e uma função auxiliares, podemos simplesmente importar e utilizar o "lru_cache", como demonstrado nas linhas em negrito abaixo:
from functools import lru_cache
def vezes_proximo(numero):
print('número =', numero)
proximo = numero + 1
print('próximo =', proximo)
resultado = numero * proximo
print('resultado =', resultado)
return resultado
Isso será o suficiente para termos:
print(vezes_proximo(2))
número = 2
próximo = 3
resultado = 6
6
print(vezes_proximo(2))
6
Mesmo sendo muito simples de implementar, essa solução é muito mais poderosa do que a desenvolvida inicialmente neste artigo, pois cuida do tamanho e do ciclo de vida dos dados armazenados no cache.
No caso, o argumento "maxsize" do decorador define o número máximo de resultados que o cache pode armazenar. Ao atingir a quantidade limite, o "lru_cache" descarta os resultados "menos recentemente usados" ("Least Recently Used"), daí o "lru_" no nome do decorador.
Nenhum comentário:
Postar um comentário