Kann mir bitte jemand helfen, zu verstehen, wie die Cache-Variable noch existiert, auch nachdem die _cachedf-Funktion zurückgegeben wurde?
Es hat mit Pythons Referenzzählung Garbage Collector zu tun. Die Variable cache
bleibt erhalten und verfügbar, da die Funktion _cachedf
einen Verweis darauf hat und der Aufrufer an cached
einen Verweis darauf hat. Wenn Sie die Funktion erneut aufrufen, verwenden Sie immer noch das gleiche Funktionsobjekt, das ursprünglich erstellt wurde. Daher haben Sie weiterhin Zugriff auf den Cache.
Sie werden den Cache nicht verlieren, bis alle Verweise darauf zerstört sind. Sie können dazu den Operator del
verwenden.
Zum Beispiel:
>>> import time
>>> def cached(f):
... cache = {} # <---- not an attribute this time!
... def _cachedf(*args):
... if args not in cache:
... cache[args] = f(*args)
... return cache[args]
... return _cachedf
...
...
>>> def foo(duration):
... time.sleep(duration)
... return True
...
...
>>> bob = cached(foo)
>>> bob(2) # Takes two seconds
True
>>> bob(2) # returns instantly
True
>>> del bob # Deletes reference to bob (aka _cachedf) which holds ref to cache
>>> bob = cached(foo)
>>> bob(2) # takes two seconds
True
>>>
Für das Protokoll, was Sie versuchen Memoization genannt acheive ist, und es gibt eine vollständigere memoizing Dekorateur aus dem decorator pattern page zur Verfügung, die die gleiche Sache tut, aber eine mit Dekorateur Klasse. Ihr Code und der klassenbasierte Dekorator sind im Wesentlichen identisch, wobei der klassenbasierte Dekorator vor dem Speichern nach der Hash-Fähigkeit sucht.
Edit (2017.02.02): @SiminJie kommentiert, dass cached(foo)(2)
zieht immer eine Verzögerung.
Dies liegt daran, cached(foo)
gibt eine neue Funktion mit einem neuen Cache zurück. Wenn cached(foo)(2)
aufgerufen wird, wird ein neuer (leerer) Cache erstellt und dann wird die zwischengespeicherte Funktion sofort aufgerufen.
Da der Cache leer ist und den Wert nicht finden kann, wird die zugrunde liegende Funktion erneut ausgeführt. Stattdessen tun Sie cached_foo = cached(foo)
und rufen Sie dann cached_foo(2)
mehrmals. Dies verursacht nur die Verzögerung für den ersten Anruf. wenn auch als Dekorateur verwendet, wird es wie erwartet:
@cached
def my_long_function(arg1, arg2):
return long_operation(arg1,arg2)
my_long_function(1,2) # incurs delay
my_long_function(1,2) # doesn't
Wenn Sie mit Dekorateure nicht vertraut sind, nehmen Sie einen Blick auf this answer zu verstehen, was die oben genannten Code bedeutet.
Vielen Dank für die Erklärung, Ihre Bearbeitung macht die Dinge sehr klar. Ich frage mich, um der Interna von (C) Python zu lernen, ob es möglich ist, auf die "Zelle" zuzugreifen, die Sie in Ihrem letzten Absatz erwähnt haben, durch Inspektion oder etwas Ähnliches? – rahmu
@rahmu: Ich korrigierte einen Fehler in der Erklärung (die sich nicht zu viel ändert). Leider sind die Zellen für Python-Code vollständig transparent und werden immer durch das Objekt ersetzt, auf das sie verweisen, so dass sie nicht untersucht werden können. –