2011-08-22 15 views
6

Ho un set di array che sono molto grandi e costosi da calcolare, e non tutti saranno necessariamente necessari dal mio codice in ogni esecuzione. Mi piacerebbe rendere la loro dichiarazione opzionale, ma idealmente senza dover riscrivere il mio intero codice.variabili pigro python? o, calcolo costoso ritardato

Esempio di come è ora:

x = function_that_generates_huge_array_slowly(0) 
y = function_that_generates_huge_array_slowly(1) 

esempio di quello che vorrei fare:

x = lambda: function_that_generates_huge_array_slowly(0) 
y = lambda: function_that_generates_huge_array_slowly(1) 
z = x * 5 # this doesn't work because lambda is a function 
     # is there something that would make this line behave like 
     # z = x() * 5? 
g = x * 6 

Durante l'utilizzo di lambda come sopra raggiunge uno degli effetti desiderati - calcolo del la matrice viene ritardata fino a quando non è necessaria, se si utilizza la variabile "x" più di una volta, deve essere calcolata ogni volta. Mi piacerebbe calcolarlo solo una volta.

MODIFICA: Dopo alcune ricerche aggiuntive, sembra che sia possibile fare ciò che voglio (approssimativamente) con attributi "pigri" in una classe (ad esempio http://code.activestate.com/recipes/131495-lazy-attributes/). Suppongo che non ci sia modo di fare qualcosa di simile senza fare una lezione separata?

EDIT2: Sto cercando di attuare alcune delle soluzioni, ma sto correndo per un problema perché non capisco la differenza tra:

class sample(object): 
    def __init__(self): 
     class one(object): 
      def __get__(self, obj, type=None): 
       print "computing ..." 
       obj.one = 1 
       return 1 
     self.one = one() 

e

class sample(object): 
    class one(object): 
     def __get__(self, obj, type=None): 
      print "computing ... " 
      obj.one = 1 
      return 1 
    one = one() 

Penso che alcune variazioni su questi sono ciò che sto cercando, dal momento che le variabili costose sono destinate a far parte di una classe.

+0

http://stackoverflow.com/questions/5078726/setting-a-property-inside-a-python-method è un'implementazione più utile della pigrizia per ciò che sto cercando di fare – keflavich

risposta

6

La prima metà del problema (riutilizzando il valore) si risolve facilmente:

class LazyWrapper(object): 
    def __init__(self, func): 
     self.func = func 
     self.value = None 
    def __call__(self): 
     if self.value is None: 
      self.value = self.func() 
     return self.value 

lazy_wrapper = LazyWrapper(lambda: function_that_generates_huge_array_slowly(0)) 

ma avete ancora di usarlo come lazy_wrapper() non lasy_wrapper.

Se avete intenzione di essere l'accesso a alcune delle variabili molte volte, può essere più veloce da usare:

class LazyWrapper(object): 
    def __init__(self, func): 
     self.func = func 
    def __call__(self): 
     try: 
      return self.value 
     except AttributeError: 
      self.value = self.func() 
      return self.value 

che renderà la prima chiamata più lento e successivi utilizzi più veloce.

Modifica: Vedo che hai trovato una soluzione simile che richiede l'utilizzo di attributi in una classe. In entrambi i casi è necessario riscrivere ogni accesso a variabili pigri, quindi scegli quello che preferisci.

Edit 2: È anche possibile fare:

class YourClass(object) 
    def __init__(self, func): 
     self.func = func 
    @property 
    def x(self): 
     try: 
      return self.value 
     except AttributeError: 
      self.value = self.func() 
      return self.value 

se si desidera accedere x come un attributo di istanza. Non è necessaria alcuna classe aggiuntiva. Se non si desidera modificare la firma della classe (richiedendo il numero func, è possibile codificare la chiamata della funzione nella proprietà.

+0

Forse se invece di '__call__ 'hai usato una proprietà, sarebbe stato possibile evitare'() 'e sarebbe stato più facile cercare/sostituire. Questo dipende dal codice esistente. – 9000

+0

Non vedo come farebbe alcuna differenza nella ricerca/sostituzione? E stai solo rimpiazzando '()' con qualcos'altro, sembra una domanda soggettiva che preferisci. Il mio metodo è il modo meno codice per implementarlo. – agf

+0

Mi piace il secondo LazyWrapper ... Gli darò un colpo. La soluzione che ho trovato aveva troppi strati di astrazione per farmi avvolgere nel contesto. – keflavich

6

scrivendo una classe è più robusto, ma l'ottimizzazione per semplicità (che credo si sta chiedendo), mi si avvicinò con la seguente soluzione:

cache = {} 

def expensive_calc(factor): 
    print 'calculating...' 
    return [1, 2, 3] * factor 

def lookup(name): 
    return (cache[name] if name in cache 
     else cache.setdefault(name, expensive_calc(2))) 

print 'run one' 
print lookup('x') * 2 

print 'run two' 
print lookup('x') * 2 
+0

Questa non è una cattiva idea dal momento che è più semplice, ma richiede più riscrittura nel mio codice. Prima proverò su LazyWrapper. – keflavich

+0

Gringo - Ho finito per usare il tuo suggerimento in aggiunta a quanto sopra (due diversi casi d'uso all'interno dello stesso codice), ma la risposta di AGF più direttamente ha risposto alla domanda che stavo chiedendo. Grazie! – keflavich

+1

Ok, ma hai chiesto espressamente una soluzione senza classi, motivo per cui mi è venuta in mente questa. Usare un defaultdict è un'altra idea che ho avuto, btw. –

1

Non si può fare un nome semplice, come x, per valutare davvero pigramente. Un nome è solo una voce in una tabella hash (ad esempio in quello che restituisce locals() o globals()). A meno che non si correggano i metodi di accesso di queste tabelle di sistema, non è possibile allegare l'esecuzione del codice alla semplice risoluzione dei nomi.

Ma è possibile eseguire il wrapping delle funzioni nei wrapper della cache in modi diversi. Questo è un modo OO:

class CachedSlowCalculation(object): 
    cache = {} # our results 

    def __init__(self, func): 
     self.func = func 

    def __call__(self, param): 
     already_known = self.cache.get(param, None) 
     if already_known: 
      return already_known 
     value = self.func(param) 
     self.cache[param] = value 
     return value 

calc = CachedSlowCalculation(function_that_generates_huge_array_slowly) 

z = calc(1) + calc(1)**2 # only calculates things once 

Questo è un modo senza classi:

def cached(func): 
    func.__cache = {} # we can attach attrs to objects, functions are objects 
    def wrapped(param): 
     cache = func.__cache 
     already_known = cache.get(param, None) 
     if already_known: 
      return already_known 
     value = func(param) 
     cache[param] = value 
     return value 
    return wrapped 

@cached 
def f(x): 
    print "I'm being called with %r" % x 
    return x + 1 

z = f(9) + f(9)**2 # see f called only once 

Nel mondo reale si aggiungerà un po 'di logica per mantenere la cache per una dimensione ragionevole, possibilmente utilizzando un algoritmo LRU .

+0

Cos'è un algoritmo LRU? Mi piace il tuo approccio al problema - è simile a quello di Gringo sopra, ma in alcuni casi forse più elegante - ma il mio problema è in realtà piuttosto ben indirizzato dagli attributi di classe. In realtà non ho bisogno di una variabile globale "x" per essere pigramente valutata; Ho bisogno di attributi di classe per essere pigramente valutato. La tua soluzione è buona per il primo caso, però. – keflavich

+0

Per curiosità, però ... è la gente del posto() dict/hashtable stessa una classe che potrebbe avere un attributo sovrascritto? Penso che sarebbe una pessima idea, ma è possibile in linea di principio? – keflavich

+0

LRU = [Least Recently Used] (http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used): una volta creata o acceduta a una voce della cache, la si inserisce all'inizio della lista; la voce meno recente utilizzata viene buttata via se l'elenco cresce troppo a lungo. Sia 'locals()' che 'globals()' restituiscono un dict basato su C con slot di metodo di sola lettura, nessuna possibilità di sovrascrivere. – 9000