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.
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
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
......