web-dev-qa-db-fra.com

Comment fonctionne Lru_cache (de functools)?

En particulier lors de l'utilisation de code récursif, des améliorations considérables ont été apportées à lru_cache. Je comprends qu'un cache est un espace qui stocke des données qui doivent être servies rapidement et qui évite à l'ordinateur de recalculer.

Comment le Python lru_cache de functools fonctionne-t-il en interne? 

Je cherche une réponse spécifique, utilise-t-il des dictionnaires comme le reste de Python? Ne stocke-t-il que la valeur return?

Je sais que Python est fortement basé sur les dictionnaires, cependant, je n’ai pas pu trouver de réponse précise à cette question. Espérons que quelqu'un pourra simplifier cette réponse pour tous les utilisateurs deStackOverflow.

8
Elvir Muslic

La source de functools est disponible ici: https://github.com/python/cpython/blob/3.6/Lib/functools.py

Le décorateur Lru_cache a le dictionnaire cache (en contexte - chaque fonction décorée a son propre dict cache) où il enregistre la valeur de retour de la fonction appelée. La clé du dictionnaire est générée avec la fonction _make_key en fonction des arguments. Ajout de quelques commentaires audacieux:

# one of decorator variants from source:
def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
    sentinel = object()      # unique object used to signal cache misses

    cache = {}                                # RESULTS SAVES HERE
    cache_get = cache.get    # bound method to lookup a key or return None
    # ...

    def wrapper(*args, **kwds):
        # Simple caching without ordering or size limit
        nonlocal hits, misses
        key = make_key(args, kwds, typed)     # BUILD A KEY FROM ARGUMENTS
        result = cache_get(key, sentinel)     # TRYING TO GET PREVIOUS CALLS RESULT
        if result is not sentinel:            # ALREADY CALLED WITH PASSED ARGUMENTS
            hits += 1
            return result                     # RETURN SAVED RESULT
                                              # WITHOUT ACTUALLY CALLING FUNCTION
        result = user_function(*args, **kwds) # FUNCTION CALL - if cache[key] empty
        cache[key] = result                   # SAVE RESULT
        misses += 1
        return result
    # ...

    return wrapper
6
ndpu

Vous pouvez vérifier le code source ici .

Il utilise essentiellement deux structures de données, un dictionary mappant les paramètres de la fonction sur son résultat et une liste liée pour suivre l'historique de vos appels de fonction.

Le cache est essentiellement mis en œuvre à l'aide des éléments suivants, ce qui est assez explicite.

cache = {}
cache_get = cache.get
....
make_key = _make_key         # build a key from the function arguments
key = make_key(args, kwds, typed)
result = cache_get(key, sentinel)

L'essentiel de la mise à jour de la liste liée est:

Elif full:

    oldroot = root
    oldroot[KEY] = key
    oldroot[RESULT] = result

    # update the linked list to pop out the least recent function call information        
    root = oldroot[NEXT]
    oldkey = root[KEY]
    oldresult = root[RESULT]
    root[KEY] = root[RESULT] = None
    ......