web-dev-qa-db-fra.com

Existe-t-il un décorateur pour simplement mettre en cache les valeurs de retour des fonctions?

Considérer ce qui suit:

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

Je suis nouveau, mais je pense que la mise en cache pourrait être prise en compte dans un décorateur. Seulement je n'en ai pas trouvé un pareil;)

PS le vrai calcul ne dépend pas des valeurs mutables

112
Tobias

À partir de Python 3.2, il existe un décorateur intégré:

@functools.lru_cache(maxsize=100, typed=False)

Decorator pour encapsuler une fonction avec un mémoizing callable qui enregistre au maximum les derniers appels. Cela permet de gagner du temps lorsqu'une fonction coûteuse ou liée aux E/S est appelée périodiquement avec les mêmes arguments .

Exemple de cache LRU pour l'informatique Nombres Fibonacci :

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

>>> print([fib(n) for n in range(16)])
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> print(fib.cache_info())
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

Si vous êtes bloqué avec Python 2.x, voici une liste d'autres bibliothèques de mémoisation compatibles:

151
Paolo Moretti

On dirait que vous ne demandez pas à un décorateur de mémoization à usage général (c'est-à-dire que vous n'êtes pas intéressé par le cas général où vous souhaitez mettre en cache renvoyer des valeurs pour différentes valeurs d'argument). C'est-à-dire que vous aimeriez avoir ceci:

x = obj.name  # expensive
y = obj.name  # cheap

alors qu'un décorateur de mémoization à usage général vous donnerait ceci:

x = obj.name()  # expensive
y = obj.name()  # cheap

Je soumets que la syntaxe d'appel de méthode est un meilleur style, car elle suggère la possibilité d'un calcul coûteux, tandis que la syntaxe de propriété suggère une recherche rapide.

[Mise à jour: le décorateur de mémoization basé sur la classe que j'avais lié et cité précédemment ne fonctionne pas pour les méthodes. Je l'ai remplacée par une fonction de décorateur.] Si vous souhaitez utiliser un décorateur de mémoization à usage général, en voici un simple:

def memoize(function):
  memo = {}
  def wrapper(*args):
    if args in memo:
      return memo[args]
    else:
      rv = function(*args)
      memo[args] = rv
      return rv
  return wrapper

Exemple d'utilisation:

@memoize
def fibonacci(n):
  if n < 2: return n
  return fibonacci(n - 1) + fibonacci(n - 2)

Un autre décorateur de mémo avec une limite de taille de cache peut être trouvé ici .

26
Nathan Kitchen
class memorize(dict):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args):
        return self[args]

    def __missing__(self, key):
        result = self[key] = self.func(*key)
        return result

Exemples d'utilisations:

>>> @memorize
... def foo(a, b):
...     return a * b
>>> foo(2, 4)
8
>>> foo
{(2, 4): 8}
>>> foo('hi', 3)
'hihihi'
>>> foo
{(2, 4): 8, ('hi', 3): 'hihihi'}
20
acmerfight

Werkzeug a un cached_property décorateur ( docs , source )

9
Imran

J'ai codé cette classe de décorateur simple pour mettre en cache les réponses des fonctions. Je trouve cela TRES utile pour mes projets:

from datetime import datetime, timedelta 

class cached(object):
    def __init__(self, *args, **kwargs):
        self.cached_function_responses = {}
        self.default_max_age = kwargs.get("default_cache_max_age", timedelta(seconds=0))

    def __call__(self, func):
        def inner(*args, **kwargs):
            max_age = kwargs.get('max_age', self.default_max_age)
            if not max_age or func not in self.cached_function_responses or (datetime.now() - self.cached_function_responses[func]['fetch_time'] > max_age):
                if 'max_age' in kwargs: del kwargs['max_age']
                res = func(*args, **kwargs)
                self.cached_function_responses[func] = {'data': res, 'fetch_time': datetime.now()}
            return self.cached_function_responses[func]['data']
        return inner

L'utilisation est simple:

import time

@cached
def myfunc(a):
    print "in func"
    return (a, datetime.now())

@cached(default_max_age = timedelta(seconds=6))
def cacheable_test(a):
    print "in cacheable test: "
    return (a, datetime.now())


print cacheable_test(1,max_age=timedelta(seconds=5))
print cacheable_test(2,max_age=timedelta(seconds=5))
time.sleep(7)
print cacheable_test(3,max_age=timedelta(seconds=5))
6
Pablo Besada

DISCLAIMER: Je suis l'auteur de kids.cache .

Vous devriez vérifier kids.cache, il fournit un décorateur @cache qui fonctionne sur python 2 et python 3. Aucune dépendance, ~ 100 lignes de code. C'est très simple à utiliser, par exemple, avec votre code en tête, vous pourriez l'utiliser comme ceci:

pip install kids.cache

Ensuite

from kids.cache import cache
...
class MyClass(object):
    ...
    @cache            # <-- That's all you need to do
    @property
    def name(self):
        return 1 + 1  # supposedly expensive calculation

Ou vous pouvez mettre le décorateur @cache après le @property (même résultat).

L'utilisation du cache sur une propriété s'appelle lazy evaluation _, kids.cache peut faire beaucoup plus (cela fonctionne avec function avec n'importe quels arguments, propriétés, n'importe quel type de méthodes et même de classes ...). Pour les utilisateurs expérimentés, kids.cache prend en charge cachetools, qui fournit des mémoires de cache fantaisie pour python 2 et python 3 (cache LRU, LFU, TTL, RR).

NOTE IMPORTANTE: le cache par défaut de kids.cache est un dict standard, ce qui n'est pas recommandé pour les programmes de longue durée avec des requêtes toujours différentes, car cela conduirait à un stockage en cache de plus en plus important. Pour cet usage, vous pouvez connecter d'autres mémoires cache en utilisant par exemple (@cache(use=cachetools.LRUCache(maxsize=2)) pour décorer votre fonction/propriété/classe/méthode ...).

6
vaab

Ah, je voulais juste trouver le bon nom pour ceci: " Évaluation de propriété paresseuse ".

Je fais beaucoup cela aussi; peut-être que j'utiliserai cette recette dans mon code un jour.

5
Ken Arnold

Si vous utilisez Framework Django, il dispose d'une telle propriété pour mettre en cache une vue ou une réponse de l'API À l'aide de @cache_page(time).

Exemple:

@cache_page(60 * 15, cache="special_cache")
def my_view(request):
    ...

Plus de détails peuvent être trouvés ici .

3
Nikhil Kumar

Il existe encore un autre exemple de décorateur memoize sur Python Wiki:

http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize

Cet exemple est un peu intelligent, car il ne mettra pas les résultats en cache si les paramètres sont mutables. (vérifiez ce code, c'est très simple et intéressant!)

3
Denilson Sá Maia

Il y a fastcache , qui est "Implémentation C de functools.lru_cache de Python 3. Fournit une accélération de 10-30x par rapport à la bibliothèque standard."

Identique à réponse choisie , juste une importation différente:

from fastcache import lru_cache
@lru_cache(maxsize=128, typed=False)
def f(a, b):
    pass

En outre, il est installé dans Anaconda , contrairement à functools pour lequel doit être installé .

2
Romi Kuntsman

En plus de l'exemple Memoize , j'ai trouvé les packages python suivants:

  • cachepy ; Il permet de configurer ttl et\ou le nombre d'appels pour les fonctions en cache; En outre, on peut utiliser un cache chiffré basé sur des fichiers ...
  • percache
2
bubble

@lru_cache n'est pas parfait avec les valeurs de fonction par défaut

mon décorateur mem:

import inspect


def get_default_args(f):
    signature = inspect.signature(f)
    return {
        k: v.default
        for k, v in signature.parameters.items()
        if v.default is not inspect.Parameter.empty
    }


def full_kwargs(f, kwargs):
    res = dict(get_default_args(f))
    res.update(kwargs)
    return res


def mem(func):
    cache = dict()

    def wrapper(*args, **kwargs):
        kwargs = full_kwargs(func, kwargs)
        key = list(args)
        key.extend(kwargs.values())
        key = hash(Tuple(key))
        if key in cache:
            return cache[key]
        else:
            res = func(*args, **kwargs)
            cache[key] = res
            return res
    return wrapper

et code pour tester:

from time import sleep


@mem
def count(a, *x, z=10):
    sleep(2)
    x = list(x)
    x.append(z)
    x.append(a)
    return sum(x)


def main():
    print(count(1,2,3,4,5))
    print(count(1,2,3,4,5))
    print(count(1,2,3,4,5, z=6))
    print(count(1,2,3,4,5, z=6))
    print(count(1))
    print(count(1, z=10))


if __== '__main__':
    main()

résultat - seulement 3 fois avec sommeil 

mais avec @lru_cache ce sera 4 fois, parce que ceci:

print(count(1))
print(count(1, z=10))

sera calculé deux fois (mauvais travail avec les valeurs par défaut)

1
Sublimer

J'ai implémenté quelque chose comme ceci, en utilisant pickle pour la persistance et en utilisant sha1 comme identifiants courts, presque certainement uniques. Fondamentalement, le cache hachait le code de la fonction et l'historique des arguments pour obtenir un sha1, puis recherchait un fichier avec ce sha1 dans le nom. S'il existait, il l'ouvrait et renvoyait le résultat. sinon, il appelle la fonction et enregistre le résultat (éventuellement uniquement si le traitement a pris un certain temps).

Cela dit, je jurerais avoir trouvé un module existant qui a fait cela et je me trouve ici en train de trouver ce module ... Le plus proche que je puisse trouver est le suivant: http: // chase-seibert. github.io/blog/2011/11/23/pythondjango-disk-based-caching-decorator.html

Le seul problème que je vois avec cela est que cela ne fonctionnerait pas bien pour les entrées de grande taille car il hache str (arg), ce qui n'est pas unique pour les tableaux géants.

Ce serait bien s'il y avait un protocole unique_hash () dans lequel une classe renvoie un hachage sécurisé de son contenu. En gros, j'ai implémenté cela manuellement pour les types qui me tenaient à cœur.

1
Ben

Essayez joblib http://pythonhosted.org/joblib/memory.html

from joblib import Memory
memory = Memory(cachedir=cachedir, verbose=0)
@memory.cache
    def f(x):
        print('Running f(%s)' % x)
        return x
0
Dror Hilman

Si vous utilisez Django et souhaitez mettre en cache des vues, voir Réponse de Nikhil Kumar


Mais si vous voulez mettre en cache TOUT résultat de fonction, vous pouvez utiliser Django-cache-utils .

Il réutilise les caches Django et fournit un décorateur cached facile à utiliser:

from cache_utils.decorators import cached

@cached(60)
def foo(x, y=0):
    print 'foo is called'
    return x+y
0
Greg Dubicki

Python 3.8 cached_property décorateur

https://docs.python.org/dev/library/functools.html#functools.cached_property

cached_property a été mentionné à: https://stackoverflow.com/a/5295190/895245 mais il sera fusionné en 3.8, ce qui est génial.

Ce décorateur peut être vu en tant que cache @property ou en tant que nettoyeur @functools.lru_cache lorsque vous n’avez pas d’argument.

Les docs disent:

@functools.cached_property(func)

Transformez une méthode d'une classe en une propriété dont la valeur est calculée une fois, puis mise en cache en tant qu'attribut normal pour la durée de vie de l'instance. Similaire à property (), avec l'ajout de la mise en cache. Utile pour les propriétés coûteuses d'instances qui sont autrement immuables.

Exemple:

class DataSet:
    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

    @cached_property
    def variance(self):
        return statistics.variance(self._data)

Nouveau dans la version 3.8.

Remarque Ce décorateur nécessite que l'attribut dict de chaque instance soit un mappage mutable. Cela signifie que cela ne fonctionnera pas avec certains types, tels que les métaclasses (puisque les attributs dict des instances de type sont des proxys en lecture seule pour l’espace de nommage de la classe), et ceux spécifiant slots sans y compris dict comme l’un des créneaux définis (en tant que telles classes ne fournissent aucun attribut dict ).