it-swarm.com.de

Wie funktioniert Lru_cache (von functools)?

Insbesondere wenn rekursiver Code verwendet wird, gibt es massive Verbesserungen mit lru_cache. Ich verstehe, dass ein Cache ein Bereich ist, in dem Daten gespeichert werden, die schnell bereitgestellt werden müssen, und den Computer vor erneuten Berechnungen bewahrt.

Wie funktioniert der Python lru_cache von functools intern? 

Ich suche nach einer bestimmten Antwort, werden Wörterbücher wie der Rest von Python verwendet? Speichert es nur den return-Wert?

Ich weiß, dass Python ist stark auf Wörterbüchern aufgebaut, jedoch konnte ich keine spezifische Antwort auf diese Frage finden. Hoffentlich kann jemand diese Antwort für alle Benutzer vonStackOverflowvereinfachen.

8
Elvir Muslic

Funktionsquellen finden Sie hier: https://github.com/python/cpython/blob/3.6/Lib/functools.py

Lru_cache decorator hat cache dictionary (im Kontext - jede dekorierte Funktion hat einen eigenen Cache-Dikt), in dem der Rückgabewert der aufgerufenen Funktion gespeichert wird. Der Wörterbuchschlüssel wird mit der _make_key-Funktion anhand von Argumenten generiert. Einige fette Kommentare hinzugefügt:

# 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

Sie können den Quellcode hier überprüfen.

Im Wesentlichen werden zwei Datenstrukturen verwendet, ein dictionary - Zuordnungsfunktionsparameter für das Ergebnis und eine linked-Liste, um den Verlauf Ihres Funktionsaufrufs zu verfolgen.

Der Cache wird im Wesentlichen mit den folgenden Elementen implementiert, was ziemlich selbsterklärend ist.

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)

Das Hauptaugenmerk beim Aktualisieren der verknüpften Liste ist:

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