2015-11-12 15 views
17

Wie kann ich den lru_cache der functools in Klassen verwenden, ohne dass Speicher verloren geht? Im folgenden minimalen Beispiel wird die foo Instanz nicht freigegeben, obwohl sie den Gültigkeitsbereich verlässt und keinen Referrer hat (außer dem lru_cache).Python functools lru_cache mit Klassenmethoden: release object

from functools import lru_cache 
class BigClass: 
    pass 
class Foo: 
    def __init__(self): 
     self.big = BigClass() 
    @lru_cache(maxsize=16) 
    def cached_method(self, x): 
     return x + 5 

def fun(): 
    foo = Foo() 
    print(foo.cached_method(10)) 
    print(foo.cached_method(10)) # use cache 
    return 'something' 

fun() 

Aber foo und damit foo.big (a BigClass) sind noch am Leben

import gc; gc.collect() # collect garbage 
len([obj for obj in gc.get_objects() if isinstance(obj, Foo)]) # is 1 

Das bedeutet, dass Foo/BigClass Instanzen noch im Speicher befinden. Selbst das Löschen von Foo (del Foo) wird sie nicht freigeben.

Warum hält sich lru_cache überhaupt an der Instanz fest? Verwendet der Cache keinen Hash und nicht das eigentliche Objekt?

Was ist die empfohlene Methode Verwenden Sie lru_caches in Klassen?

Ich weiß von zwei Lösungen: Use per instance caches oder make the cache ignore object (was zu falschen Ergebnissen führen könnte, obwohl)

Antwort

16

Dies ist nicht die sauberste Lösung, aber es ist völlig transparent für den Programmierer:

import functools 
import weakref 

def memoized_method(*lru_args, **lru_kwargs): 
    def decorator(func): 
     @functools.wraps(func) 
     def wrapped_func(self, *args, **kwargs): 
      # We're storing the wrapped method inside the instance. If we had 
      # a strong reference to self the instance would never die. 
      self_weak = weakref.ref(self) 
      @functools.wraps(func) 
      @functools.lru_cache(*lru_args, **lru_kwargs) 
      def cached_method(*args, **kwargs): 
       return func(self_weak(), *args, **kwargs) 
      setattr(self, func.__name__, cached_method) 
      return cached_method(*args, **kwargs) 
     return wrapped_func 
    return decorator 

Es dauert genau die gleichen Parameter wie lru_cache und funktioniert genau gleich. Es übergibt jedoch niemals self an lru_cache und stattdessen eine pro-Instanz lru_cache.