2009-05-02 13 views
92

Si consideri il seguente:C'è un decoratore semplicemente per memorizzare i valori di ritorno della funzione?

@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 memorizzazione nella cache potrebbe essere preso in considerazione fuori in un decoratore. Solo non ho trovato uno simile;)

PS il calcolo vero e proprio non dipende da valori mutevoli

+0

Ci può essere un decoratore là fuori che ha una certa capacità del genere, ma si rifugio ho accuratamente specificato ciò che vuoi Che tipo di backend di cache stai usando? E come sarà il valore inserito? Sto assumendo dal tuo codice che quello che stai veramente chiedendo è una proprietà di sola lettura memorizzata nella cache. –

+0

Ci sono decoratori memoizing che eseguono ciò che chiamate "cache"; di solito lavorano su funzioni in quanto tali (sia che vogliano diventare metodi o meno) i cui risultati dipendono dai loro argomenti (non su cose mutevoli come il sé!) e quindi mantengono un appunto separato. –

risposta

24

Sembra che tu sei non chiedendo una Memoizzazione decoratore general-purpose (vale a dire, non ti interessa il caso generale in cui desideri memorizzare nella cache i valori di ritorno per i diversi valori degli argomenti). Cioè, si desidera avere questo:

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

mentre decoratore general-purpose Memoizzazione darebbe questo:

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

Io sostengo che la sintassi del metodo-call è lo stile migliore, perché suggerisce la possibilità di un calcolo costoso mentre la sintassi della proprietà suggerisce una rapida ricerca.

[Aggiornamento: Il decoratore di memoizzazione basato su classi che avevo collegato e citato in precedenza non funziona per i metodi. Ho sostituito con una funzione decoratore] Se siete disposti a utilizzare un Memoizzazione generico decoratore, qui è semplice:. L'uso

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:

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

Un'altra Memoizzazione decoratore con un limite sulla dimensione della cache può essere trovato here.

+0

Nessuno dei decoratori menzionati in tutte le risposte lavora per i metodi! Probabilmente perché sono basati su classi. È passato solo un io? Altri funzionano bene, ma è crufty per memorizzare i valori nelle funzioni. – Tobias

+2

Penso che potresti incontrare un problema se args non è lavabile. – Unknown

+1

@Unknown Sì, il primo decoratore che ho citato qui è limitato ai tipi hashable. Quello in ActiveState (con il limite della dimensione della cache) mette gli argomenti in una stringa (hashable) che è ovviamente più costosa ma più generale. –

5

Ah, ho solo bisogno di trovare il nome giusto per questo: "Lazy property evaluation".

Anche io lo faccio molto; forse userò quella ricetta nel mio codice qualche volta.

3

C'è ancora un altro esempio di un Memoize decoratore in Python Wiki:

http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize

Questo esempio è un po 'intelligente, perché non cache i risultati se i parametri sono mutabili. (Verificare che il codice, è molto semplice ed interessante!)

8

Werkzeug ha un cached_property decoratore (docs, source)

+0

Sì. Vale la pena distinguere dal caso generale di memoizzazione, in quanto la memoizzazione standard non funziona se la classe non è lavabile. –

115

A partire da Python 3.2 v'è un built-in decorator:

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

Decoratore per avvolgere una funzione con un memoizing callable che consente di salvare le chiamate più recenti di max. Può far risparmiare tempo quando una funzione costosa o I/O legata viene periodicamente chiamata con gli stessi argomenti.

Esempio di una cache LRU per il calcolo Fibonacci numbers:

@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 sei bloccato con Python 2.x, ecco una lista di altre biblioteche Memoizzazione compatibili:

+1

Backport http://code.activestate.com/recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/ – Kos

+1

Si noti che questo funziona solo per argomenti di funzione immutabili. – gerrit

+0

il backport ora può essere trovato qui: https://pypi.python.org/pypi/backports.functools_lru_cache –

2

Se si utilizza Django quadro, ha una tale proprietà per memorizzare nella cache una vista o una risposta di usando @cache_page API (tempo) e ci possono essere anche altre opzioni.

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

Maggiori dettagli possono essere trovati here.

1

Ho implementato qualcosa di simile, usando pickle per la persistenza e usando sha1 per ID brevi quasi certamente unici. Fondamentalmente la cache ha cancellato il codice della funzione e la cronologia degli argomenti per ottenere uno sha1, quindi ha cercato un file con quello sha1 nel nome. Se esisteva, lo apriva e restituiva il risultato; in caso contrario, chiama la funzione e salva il risultato (facoltativamente salvando solo se è necessario un certo periodo di tempo per l'elaborazione).

Detto questo, io giurerei ho trovato un modulo esistente che ha fatto questo e mi ritrovo qui cercando di trovare quel modulo ... Il più vicino che posso trovare è questo, che sembra circa la destra: http://chase-seibert.github.io/blog/2011/11/23/pythondjango-disk-based-caching-decorator.html

Il l'unico problema che vedo con questo è che non funzionerebbe bene per gli input di grandi dimensioni dal momento che hashes str (arg), che non è univoco per gli array giganti.

Sarebbe bello se ci fosse un unique_hash() protocollo che aveva un ritorno di classe un hash sicuro del suo contenuto. Fondamentalmente l'ho implementato manualmente per i tipi a cui tenevo.

17
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 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'} 
+0

Bella soluzione, ha qualche problema? – iago1460

+0

Strano! Come funziona? Non sembra come altri decoratori che ho visto. – PascalVKooten

+1

Questa soluzione restituisce un errore TypeE se si utilizzano gli argomenti delle parole chiave, ad es. foo (3, b = 5) – kadee

4

NOTA BENE: Sono l'autore di kids.cache.

È necessario controllare kids.cache, fornisce un decoratore @cache che funziona su python 2 e python 3. Nessuna dipendenza, ~ 100 righe di codice.E 'molto semplice da usare, per esempio, con il codice in mente, si potrebbe usare in questo modo:

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 

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

Utilizzando la cache su una proprietà si chiama pigro valutazione, kids.cache può fare molto di più (funziona su funzione con argomenti, proprietà, qualsiasi tipo di metodi, e anche le classi ...). Per gli utenti avanzati, kids.cache supporta cachetools che fornisce archivi di cache di fantasia per python 2 e python 3 (LRU, LFU, TTL, cache RR).

+0

Questo modulo sembra avere un tempo di importazione lento su python 2 ~ 0.9s (vedi: https://pastebin.com/raw/aA1ZBE9Z). Sospetto che ciò sia dovuto a questa linea https://github.com/0k/kids.cache/blob/master/src/kids/__init__.py#L3 (c.f punti di ingresso setuptools). Sto creando un problema per questo. –

+0

Ecco un problema per quanto sopra https://github.com/0k/kids.cache/issues/9. –

6

Ho codificato questa semplice classe decoratore per memorizzare le risposte della funzione. 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)) 
+1

La prima '@ cache 'manca di parentesi. Altrimenti restituirà solo l'oggetto 'cache' al posto di' myfunc' e quando viene chiamato come 'myfunc()' allora 'inner' sarà sempre restituito come valore di ritorno –

2

Insieme con la Memoize Example ho trovato i seguenti pacchetti python:

  • cachepy; Permette di impostare ttl e \ o il numero di chiamate per le funzioni memorizzate nella cache; Inoltre, si può usare la cache basata su file crittografato ...
  • percache
Problemi correlati