web-dev-qa-db-fra.com

Qu'est-ce que la mémorisation et comment puis-je l'utiliser en Python?

Je viens juste de commencer Python et je n'ai aucune idée de ce que mémorisation et comment l'utiliser. Aussi, puis-je avoir un exemple simplifié?

344
blur959

La mémorisation se réfère effectivement à la mémorisation ("mémorisation" → "mémorandum" → à retenir) des résultats d'appels de méthode basés sur les entrées de la méthode, puis au résultat mémorisé plutôt que de le calculer à nouveau. Vous pouvez le considérer comme un cache pour les résultats de la méthode. Pour plus de détails, voir page 387 pour la définition dans Introduction aux algorithmes (3e), Cormen et Al.

Un exemple simple de calcul de factorielles utilisant la mémorisation dans Python ressemblerait à ceci:

factorial_memo = {}
def factorial(k):
    if k < 2: return 1
    if k not in factorial_memo:
        factorial_memo[k] = k * factorial(k-1)
    return factorial_memo[k]

Vous pouvez devenir plus compliqué et encapsuler le processus de mémorisation dans une classe:

class Memoize:
    def __init__(self, f):
        self.f = f
        self.memo = {}
    def __call__(self, *args):
        if not args in self.memo:
            self.memo[args] = self.f(*args)
        #Warning: You may wish to do a deepcopy here if returning objects
        return self.memo[args]

Ensuite:

def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

factorial = Memoize(factorial)

Une fonctionnalité appelée " décorateurs " a été ajoutée dans Python 2.4, ce qui vous permet d'écrire simplement ce qui suit pour accomplir la même chose:

@Memoize
def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

La bibliothèque de décorateurs Python possède un décorateur similaire appelé memoized qui est légèrement plus robuste que la classe Memoize présentée ici.

333
jason

La nouveauté de Python 3.2 est functools.lru_cache . Par défaut, seuls les 128 derniers appels utilisés sont mis en cache, mais vous pouvez définir le paramètre maxsize sur None pour indiquer que le cache ne doit jamais expirer:

_import functools

@functools.lru_cache(maxsize=None)
def fib(num):
    if num < 2:
        return num
    else:
        return fib(num-1) + fib(num-2)
_

Cette fonction en soi est très lente, essayez fib(36) et vous devrez attendre environ dix secondes.

L'ajout de l'annotation _lru_cache_ permet de s'assurer que si la fonction a été appelée récemment pour une valeur particulière, elle ne recalculera pas cette valeur, mais utilisera un résultat précédent mis en cache. Dans ce cas, cela entraîne une amélioration considérable de la vitesse, alors que le code n’est pas encombré par les détails de la mise en cache.

203
Flimm

Les autres réponses couvrent ce que c'est très bien. Je ne répète pas ça. Juste quelques points qui pourraient vous être utiles.

En général, la mémorisation est une opération que vous pouvez appliquer à toute fonction qui calcule quelque chose (cher) et renvoie une valeur. Pour cette raison, il est souvent implémenté en tant que décorateur . La mise en œuvre est simple et ce serait quelque chose comme ça

memoised_function = memoise(actual_function)

ou exprimé en tant que décorateur

@memoise
def actual_function(arg1, arg2):
   #body
59
Noufal Ibrahim

La mémorisation consiste à conserver les résultats de calculs coûteux et à renvoyer le résultat mis en cache plutôt que de le recalculer en permanence.

Voici un exemple:

def doSomeExpensiveCalculation(self, input):
    if input not in self.cache:
        <do expensive calculation>
        self.cache[input] = result
    return self.cache[input]

Une description plus complète peut être trouvée dans le entrée de Wikipédia sur la mémoisation .

18
Bryan Oakley

N'oublions pas la fonction intégrée hasattr, pour ceux qui souhaitent créer à la main. De cette façon, vous pouvez conserver le cache de mémoire dans la définition de la fonction (par opposition à une définition globale).

def fact(n):
    if not hasattr(fact, 'mem'):
        fact.mem = {1: 1}
    if not n in fact.mem:
        fact.mem[n] = n * fact(n - 1)
    return fact.mem[n]
15
Karel Kubat

J'ai trouvé cela extrêmement utile

def memoize(function):
    from functools import wraps

    memo = {}

    @wraps(function)
    def wrapper(*args):
        if args in memo:
            return memo[args]
        else:
            rv = function(*args)
            memo[args] = rv
            return rv
    return wrapper


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

fibonacci(25)
10
mr.bjerre

La mémorisation consiste essentiellement à enregistrer les résultats des opérations passées effectuées avec des algorithmes récursifs afin de réduire le besoin de traverser l'arbre de récurrence si le même calcul est requis à un stade ultérieur.

voir http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/

Exemple de mémorisation Fibonacci en Python:

fibcache = {}
def fib(num):
    if num in fibcache:
        return fibcache[num]
    else:
        fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
        return fibcache[num]
6
Romaine Carter

Eh bien, je devrais d'abord répondre à la première partie: qu'est-ce que la mémoisation?

C'est juste une méthode pour échanger de la mémoire contre du temps. Pensez à Table de multiplication .

L'utilisation d'un objet mutable comme valeur par défaut dans Python est généralement considérée comme incorrecte. Mais si vous l'utilisez judicieusement, il peut être utile d'implémenter un memoization.

Voici un exemple adapté de http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects

En utilisant un mutable dict dans la définition de la fonction, les résultats calculés intermédiaires peuvent être mis en cache (par exemple, lors du calcul de factorial(10) après le calcul factorial(9), nous pouvons réutiliser tous les résultats intermédiaires)

def factorial(n, _cache={1:1}):    
    try:            
        return _cache[n]           
    except IndexError:
        _cache[n] = factorial(n-1)*n
        return _cache[n]
5
yegle

La mémorisation est la conversion de fonctions en structures de données. Habituellement, on souhaite que la conversion se fasse de manière incrémentielle et paresseuse (à la demande d'un élément de domaine donné - ou "clé"). Dans les langages fonctionnels paresseux, cette conversion paresseuse peut s'effectuer automatiquement et, par conséquent, la mémorisation peut être implémentée sans effets secondaires (explicites).

5
Conal

Voici une solution qui fonctionnera avec des arguments de type liste ou dict sans se plaindre:

def memoize(fn):
    """returns a memoized version of any function that can be called
    with the same list of arguments.
    Usage: foo = memoize(foo)"""

    def handle_item(x):
        if isinstance(x, dict):
            return make_Tuple(sorted(x.items()))
        Elif hasattr(x, '__iter__'):
            return make_Tuple(x)
        else:
            return x

    def make_Tuple(L):
        return Tuple(handle_item(x) for x in L)

    def foo(*args, **kwargs):
        items_cache = make_Tuple(sorted(kwargs.items()))
        args_cache = make_Tuple(args)
        if (args_cache, items_cache) not in foo.past_calls:
            foo.past_calls[(args_cache, items_cache)] = fn(*args,**kwargs)
        return foo.past_calls[(args_cache, items_cache)]
    foo.past_calls = {}
    foo.__= 'memoized_' + fn.__name__
    return foo

Notez que cette approche peut naturellement être étendue à n’importe quel objet en implémentant votre propre fonction de hachage en tant que cas particulier dans handle_item. Par exemple, pour que cette approche fonctionne avec une fonction prenant un ensemble en tant qu'argument d'entrée, vous pouvez ajouter à handle_item:

if is_instance(x, set):
    return make_Tuple(sorted(list(x)))
4
RussellStewart

Solution fonctionnant avec des arguments de position et de mot clé indépendamment de l'ordre dans lequel les arguments de mot clé ont été transmis (à l'aide de inspect.getargspec ):

import inspect
import functools

def memoize(fn):
    cache = fn.cache = {}
    @functools.wraps(fn)
    def memoizer(*args, **kwargs):
        kwargs.update(dict(Zip(inspect.getargspec(fn).args, args)))
        key = Tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
        if key not in cache:
            cache[key] = fn(**kwargs)
        return cache[key]
    return memoizer

Question similaire: Identifier les fonctions équivalentes de varargs appelle une mémoisation en Python

3
ndpu

Je voulais juste ajouter aux réponses déjà fournies, la bibliothèque de décorateurs Python possède des implémentations simples mais utiles qui peuvent également mémoriser des "types non contraignants", à la différence de functools.lru_cache.

2
Sid
cache = {}
def fib(n):
    if n <= 1:
        return n
    else:
        if n not in cache:
            cache[n] = fib(n-1) + fib(n-2)
        return cache[n]
2
Vikrant Singh