C’è un decoratore semplicemente cache funzione dei valori di ritorno?

Si consideri il seguente:

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

Io sono nuovo, ma penso che la cache potrebbe essere fattorizzato in un decoratore. Solo non mi piace 😉

PS il calcolo vero e proprio non dipendono i valori modificabili

  • Ci può essere un decoratore là fuori che ha una certa capacità di simile, ma non completamente specificata ciò che si desidera. Che tipo di memorizzazione nella cache di backend stai usando? E come sarà il valore digitato? Sto assumendo da codice che cosa si sta realmente chiedendo è la cache di una proprietà di sola lettura.
  • Ci sono memoizing decoratori che eseguire ciò che è chiamata “cache”; di solito lavoro su funzioni come tale (se vuole diventare metodi o non), i cui risultati dipendono da loro argomenti (non modificabile cose come auto!-) e in modo da tenere separati memo-dict.
InformationsquelleAutor Tobias | 2009-05-02

 

16 Replies
  1. 178

    A partire da Python 3.2 vi è un built-in decoratore:

    @functools.lru_cache(maxsize=100, digitato=False)

    Decoratore per avvolgere una funzione con un memoizing callable che consente di risparmiare fino al maxsize chiamate più recenti. È possibile risparmiare tempo quando un costoso o I/O bound funzione chiamata periodicamente con gli stessi argomenti.

    Esempio di LRU cache per il calcolo I numeri di Fibonacci:

    @lru_cache(maxsize=None)
    def fib(n):
        if n < 2:
            return n
        return fib(n-1) + fib(n-2)
    
    >>> print([fib(n) for n in range(16)])
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
    
    >>> print(fib.cache_info())
    CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

    Se si sono bloccati con Python 2.x, ecco qui di seguito un elenco di altri compatibili memoization librerie:

    • Backport code.activestate.com/recipes/…
    • Si noti che questo funziona solo per immutabile gli argomenti della funzione.
    • il backport può ora essere trovato qui: pypi.python.org/pypi/backports.functools_lru_cache
    • in teoria funziona per hashable oggetti in generale, anche se alcuni hashable oggetti sono solo uguali anche se sono lo stesso oggetto (come oggetti definiti dall’utente senza un esplicito __hash__() la funzione).
    • Funziona, ma a torto. Se mi passa un hashable, mutevole argomento, e modificare il valore dell’oggetto dopo la prima chiamata della funzione, la seconda chiamata restituirà l’cambiato, non è l’originale, oggetto. Che è quasi certamente non quello che vuole l’utente. Per farlo funzionare per la mutabile, gli argomenti richiederebbe lru_cache per fare una copia di ciò che il risultato è la cache, e non tale copia è stata fatta in functools.lru_cache attuazione. Così facendo, inoltre, rischiano di creare hard-to-find problemi di memoria quando viene utilizzato per la cache di un oggetto di grandi dimensioni.
    • Ti dispiacerebbe riportiamo qui di seguito: stackoverflow.com/questions/44583381/… ? Io non interamente seguire il tuo esempio.
    • Ora non so. Ho il sospetto che il mio discorso vale per il mio tentativo di fare una funzione di cache che funziona con NumPy array, non per il built-in lru_cache funzione. Penso mutevole hashable oggetti sono piuttosto rare in ogni caso. Scusate per la confusione!!!

  2. 27

    Sembra che tu stia non chiedere un general-purpose memoization decoratore (cioè, non siete interessati al caso generale in cui si desidera memorizzare i valori di ritorno per diversi valori dell’argomento). Che è, ti piacerebbe avere questo:

    x = obj.name  # expensive
    y = obj.name  # cheap

    mentre un general-purpose memoization decoratore di dare è questo:

    x = obj.name()  # expensive
    y = obj.name()  # cheap

    Credo che il metodo-la sintassi di chiamata è un migliore stile, perché suggerisce la possibilità di costosi calcolo, mentre la sintassi della proprietà suggerisce una ricerca rapida.

    [Aggiornamento: La classe base memoization decoratore avevo legato e qui citato in precedenza non funziona per i metodi. L’ho sostituito con un decoratore funzione.] Se siete disposti a utilizzare un general-purpose memoization decoratore, ecco un semplice:

    def memoize(function):
      memo = {}
      def wrapper(*args):
        if args in memo:
          return memo[args]
        else:
          rv = function(*args)
          memo[args] = rv
          return rv
      return wrapper

    Esempio di utilizzo:

    @memoize
    def fibonacci(n):
      if n < 2: return n
      return fibonacci(n - 1) + fibonacci(n - 2)

    Un altro memoization decoratore con un limite sulla dimensione della cache può essere trovato qui.

    • Nessuno dei decoratori menzionato in tutte le risposte di lavoro per metodi! Probabilmente perché sono di classe. Una sola auto è passata? Altri funzionano bene, ma è crufty per memorizzare i valori in funzioni.
    • Penso che si può incorrere in un problema se l’argomento non è hashable.
    • Sì, il primo decoratore che ho citato qui è limitato a hashable tipi. L’uno a ActiveState (con il limite delle dimensioni della cache) sottaceti gli argomenti in un (hashable) stringa che è ovviamente più costosa, ma più generale.
    • Grazie per aver ricordato i limiti della classe a base di decoratori. Ho rivisto la mia risposta a mostrare un decoratore funzione, che lavora per i metodi (in realtà ho testato questo).
    • Si potrebbe utilizzare un MD5 digest del args per rendere hash-in grado. Non so se è super performante o meno.
    • C’è anche il problema della gestione delle chiamate con la parola chiave argomenti: questa soluzione funziona in questo caso.
    • Puoi spiegare come la cache di lavoro? Perché si inizializza il memo = {} in funzione memorize. Poi quando chiami diversi fibonacci , il fibonacci s chiamare lo stesso decoratore memorize?
    • Il decoratore è chiamato solo una volta, e avvolto funzione restituisce è lo stesso utilizzato per tutte le diverse chiamate per fibonacci. La funzione utilizza sempre la stessa memo dizionario.
    • Mi dispiace per il downvote. Non so quando è successo, ma devo aver cliccato involontariamente.

  3. 22
    class memorize(dict):
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args):
            return self[args]
    
        def __missing__(self, key):
            result = self[key] = self.func(*key)
            return result

    Esempio si utilizza:

    >>> @memorize
    ... def foo(a, b):
    ...     return a * b
    >>> foo(2, 4)
    8
    >>> foo
    {(2, 4): 8}
    >>> foo('hi', 3)
    'hihihi'
    >>> foo
    {(2, 4): 8, ('hi', 3): 'hihihi'}
    • Strano! Come si fa questo lavoro? Non sembra come gli altri decoratori ho visto.
    • Questa soluzione restituisce un TypeError se uno usa la parola chiave di argomenti, ad esempio pippo(3, b=5)
    • Il problema della soluzione, è che non hanno un limite di memoria. Come per gli argomenti denominati, si può solo aggiungere a __ call__ e __ mancanti__ come **nargs
  4. 9

    Ho codificato questo semplice decoratore classe a funzione di cache le risposte. Io la trovo MOLTO utile per i miei progetti:

    from datetime import datetime, timedelta 
    
    class cached(object):
        def __init__(self, *args, **kwargs):
            self.cached_function_responses = {}
            self.default_max_age = kwargs.get("default_cache_max_age", timedelta(seconds=0))
    
        def __call__(self, func):
            def inner(*args, **kwargs):
                max_age = kwargs.get('max_age', self.default_max_age)
                if not max_age or func not in self.cached_function_responses or (datetime.now() - self.cached_function_responses[func]['fetch_time'] > max_age):
                    if 'max_age' in kwargs: del kwargs['max_age']
                    res = func(*args, **kwargs)
                    self.cached_function_responses[func] = {'data': res, 'fetch_time': datetime.now()}
                return self.cached_function_responses[func]['data']
            return inner

    L’utilizzo è molto semplice:

    import time
    
    @cached
    def myfunc(a):
        print "in func"
        return (a, datetime.now())
    
    @cached(default_max_age = timedelta(seconds=6))
    def cacheable_test(a):
        print "in cacheable test: "
        return (a, datetime.now())
    
    
    print cacheable_test(1,max_age=timedelta(seconds=5))
    print cacheable_test(2,max_age=timedelta(seconds=5))
    time.sleep(7)
    print cacheable_test(3,max_age=timedelta(seconds=5))
    • Il primo @cached manca la parentesi. Altrimenti saranno solo restituire il cached oggetto di myfunc e quando è stato chiamato come myfunc() poi inner verrà sempre restituito come valore di ritorno
  5. 7

    Python 3.8 cached_property decoratore

    https://docs.python.org/dev/library/functools.html#functools.cached_property

    cached_property da Werkzeug è stato menzionato all’: https://stackoverflow.com/a/5295190/895245 ma presumibilmente versione derivata unita 3.8, che è impressionante.

    Questo decoratore può essere visto come la memorizzazione nella cache @property, o come addetto @functools.lru_cache per quando non si hanno argomenti.

    Docs dire:

    @functools.cached_property(func)

    Trasformare un metodo di una classe in una proprietà il cui valore è calcolato una sola volta e quindi memorizzato nella cache come un normale attributo per la vita dell’istanza. Simile per struttura(), con l’aggiunta di memorizzazione nella cache. Utile per costoso calcolata proprietà di istanze che, altrimenti, sarebbe effettivamente immutabile.

    Esempio:

    class DataSet:
        def __init__(self, sequence_of_numbers):
            self._data = sequence_of_numbers
    
        @cached_property
        def stdev(self):
            return statistics.stdev(self._data)
    
        @cached_property
        def variance(self):
            return statistics.variance(self._data)

    Nuovo nella versione 3.8.

    Nota Questo decoratore richiede che il dict attributo in ogni istanza a essere un mutevole mappatura. Questo significa che non funzionerà con alcuni tipi, come metaclasses (dal dict attributi di tipo istanze sono di sola lettura deleghe per la classe di spazio dei nomi), e quelli che specificano slot senza dict come uno dei definiti slot (come tali classi non forniscono un dict attributo a tutti).

  6. 6

    DISCLAIMER: io sono l’autore di ragazzi.cache.

    Si dovrebbe verificare kids.cache, esso fornisce un @cache decoratore che funziona su python 2 e python 3. Dipendenze, ~100 righe di codice. E ‘ molto semplice da usare, per esempio, con il codice in mente, si potrebbe utilizzare come questa:

    pip install kids.cache

    Poi

    from kids.cache import cache
    ...
    class MyClass(object):
        ...
        @cache            # <-- That's all you need to do
        @property
        def name(self):
            return 1 + 1  # supposedly expensive calculation

    O si potrebbe mettere il @cache decoratore dopo il @property (stesso risultato).

    L’utilizzo della cache su una proprietà è chiamato lazy evaluation, kids.cache può fare molto di più (funziona su una funzione con argomenti, proprietà, qualsiasi tipo di metodi, e anche le classi…). Per gli utenti avanzati, kids.cache supporta cachetools che fornisce fantasia cache memorizza per python 2 e python 3 (LRU, LFU, TTL, RR cache).

    NOTA IMPORTANTE: la cache predefinita negozio di kids.cache è uno standard dict, che non è raccomandato per il lungo programma in esecuzione, con sempre diverse query in quanto porterebbe ad una sempre crescente di memorizzazione nella cache store. Per questo tipo di utilizzo è possibile plugin altre cache memorizza usando, per esempio, (@cache(use=cachetools.LRUCache(maxsize=2)) per decorare la vostra funzione/struttura/classe/metodo…)

    • Questo modulo sembra essere il risultato di un lento importa il tempo su python 2 ~0.9 s (vedi: pastebin.com/raw/aA1ZBE9Z). Ho il sospetto che questo è a causa di questa linea github.com/0k/kids.cache/blob/master/src/kids/__init__.py#L3 (c.f setuptools punti di ingresso). Sto creando un problema per questo.
    • Qui è un problema di cui sopra github.com/0k/kids.cache/issues/9 .
    • Questo sarebbe porta alla perdita di memoria.
    • creare un’istanza c di MyClass, e controllare con objgraph.show_backrefs([c], max_depth=10), c’è un ref catena dalla classe object MyClass per c. Che dire, c non sarebbe mai stato rilasciato fino a quando il MyClass stato rilasciato.
    • siete invitati e benvenuti ad aggiungere le vostre preoccupazioni in github.com/0k/kids.cache/issues/10 . Stackoverflow non è il posto giusto per una corretta discussione su questo. E ulteriori chiarimenti sono necessari. Grazie per il tuo feedback.
  7. 3

    Se si utilizza il Framework Django, ha una proprietà di questo tipo di cache di vista o di risposta delle API
    utilizzando @cache_page(time) e ci possono essere altre opzioni come bene.

    Esempio:

    @cache_page(60 * 15, cache="special_cache")
    def my_view(request):
        ...

    Ulteriori dettagli possono essere trovati qui.

  8. 2

    Insieme con il Memoize Esempio ho trovato i seguenti pacchetti di python:

    • cachepy; Permette di impostare ttl e\o il numero di chiamate per la cache di funzioni; Inoltre, si può utilizzare un file crittografato a base di cache…
    • percache
  9. 1

    Ho implementato qualcosa di simile a questo, con la salamoia per la costanza e l’utilizzo sha1 per brevi quasi certamente-Id univoci. Fondamentalmente la cache hash il codice della funzione e l’hist di argomenti per avere una sha1 poi ho cercato un file con un sha1 nel nome. Se esistesse, si è aperto ed è tornato il risultato; se non lo è, si chiama la funzione e salva il risultato (facoltativamente solo di salvare se ha preso una certa quantità di tempo per il processo).

    Detto questo, mi piacerebbe giuro ho trovato un modulo esistente che ha fatto questo e mi trovo qui a cercare di trovare quel modulo… Il più vicino che posso trovare è questo, che sembra sul diritto: http://chase-seibert.github.io/blog/2011/11/23/pythondjango-disk-based-caching-decorator.html

    L’unico problema che vedo è che non funziona bene per le grandi ingressi in quanto hash str(arg), che non è unico per il gigante di matrici.

    Sarebbe bello se ci fosse un unique_hash() protocollo che aveva una classe in un secure hash del suo contenuto. In pratica ho implementato manualmente che per le tipologie di cui mi interessasse.

  10. 1

    @lru_cache non è perfetto con funzione di default i valori

    mio mem decoratore:

    import inspect
    
    
    def get_default_args(f):
        signature = inspect.signature(f)
        return {
            k: v.default
            for k, v in signature.parameters.items()
            if v.default is not inspect.Parameter.empty
        }
    
    
    def full_kwargs(f, kwargs):
        res = dict(get_default_args(f))
        res.update(kwargs)
        return res
    
    
    def mem(func):
        cache = dict()
    
        def wrapper(*args, **kwargs):
            kwargs = full_kwargs(func, kwargs)
            key = list(args)
            key.extend(kwargs.values())
            key = hash(tuple(key))
            if key in cache:
                return cache[key]
            else:
                res = func(*args, **kwargs)
                cache[key] = res
                return res
        return wrapper

    e il codice per il test:

    from time import sleep
    
    
    @mem
    def count(a, *x, z=10):
        sleep(2)
        x = list(x)
        x.append(z)
        x.append(a)
        return sum(x)
    
    
    def main():
        print(count(1,2,3,4,5))
        print(count(1,2,3,4,5))
        print(count(1,2,3,4,5, z=6))
        print(count(1,2,3,4,5, z=6))
        print(count(1))
        print(count(1, z=10))
    
    
    if __name__ == '__main__':
        main()

    risultato – solo 3 volte con il sonno

    ma con @lru_cache sarà 4 volte, a causa di questo:

    print(count(1))
    print(count(1, z=10))

    sarà calcolato due volte (un po ‘ con le impostazioni predefinite)

  11. 0

    Se si utilizza Django e desidera cache di vista, vedere Nikhil Kumar risposta.

    Ma se si desidera memorizzare QUALSIASI risultato di una funzione, è possibile utilizzare django-cache-utils.

    Riutilizza Django cache e fornisce un facile da usare cached decoratore:

    from cache_utils.decorators import cached
    
    @cached(60)
    def foo(x, y=0):
        print 'foo is called'
        return x+y

Lascia un commento