2011-08-22 23 views
6

Tengo un conjunto de matrices que son muy grandes y costosas de computar, y no necesariamente todo será necesario para mi código en cualquier ejecución dada. Me gustaría hacer su declaración opcional, pero idealmente sin tener que volver a escribir todo el código.python variables perezosas? o, cómputo caro retrasado

Ejemplo de cómo es ahora:

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

Ejemplo de lo que me gustaría hacer:

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 el uso de lambda que el anterior alcanza uno de los efectos deseados - cálculo de la la matriz se retrasa hasta que sea necesaria; si utiliza la variable "x" más de una vez, debe calcularse cada vez. Me gustaría calcularlo solo una vez.

EDIT: Después de algunas búsquedas adicionales, parece que es posible hacer lo que quiero (aproximadamente) con atributos "flojos" en una clase (por ejemplo, http://code.activestate.com/recipes/131495-lazy-attributes/). Supongo que no hay forma de hacer algo similar sin hacer una clase por separado.

Edit2: estoy tratando de poner en práctica algunas de las soluciones, pero estoy corriendo para un problema porque no entiendo la diferencia entre:

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() 

y

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

Creo que esto es lo que estoy buscando, ya que las costosas variables pretenden ser parte de una clase.

+0

http://stackoverflow.com/questions/5078726/setting-a-property-inside-a-python-method es una implementación más útil de la pereza para lo que estoy tratando de hacer – keflavich

Respuesta

6

La primera mitad de su problema (reutilizando el valor) es fácil de resolver:

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)) 

pero todavía tiene que utilizarlo como lazy_wrapper() no lasy_wrapper.

Si vas a ser el acceso a algunas de las variables que muchas veces, puede ser más rápido de usar:

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 

lo que hará que la primera llamada más lento y más rápido los usos posteriores.

Edición: Veo que encontró una solución similar que requiere el uso de atributos en una clase. De cualquier manera, es necesario volver a escribir cada acceso variable variable, de modo que solo elija el que desee.

Edición 2: También puede hacer:

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 

si desea acceder a la x como un atributo de instancia. No se necesita clase adicional. Si no desea cambiar la firma de clase (haciendo que requiera func, puede codificar la llamada a la función en la propiedad.

+0

Tal vez si en lugar de '__call__ 'Usaste una propiedad, sería posible evitar'() 'y sería más fácil buscar/reemplazar. Esto depende del código existente. – 9000

+0

No veo cómo haría ninguna diferencia en la búsqueda/reemplazo? Y solo estás reemplazando '()' con otra cosa, parece una pregunta subjetiva que prefieres. Mi método es la forma menos código de implementarlo. – agf

+0

Me gusta el segundo LazyWrapper ... Lo probaré. La solución que encontré tenía demasiadas capas de abstracción para que pudiera entender mi contexto. – keflavich

6

escribir una clase es más robusto, pero la optimización de la simplicidad (que creo que está solicitando), se me ocurrió la siguiente solución:

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

No es una mala idea, ya que es más simple, pero requiere más reescritura en mi código. Voy a probar el LazyWrapper anterior primero. – keflavich

+0

Gringo: terminé usando su sugerencia además de la anterior (dos casos de uso diferentes dentro del mismo código), pero la respuesta de agf respondió más directamente a la pregunta que estaba formulando. ¡Gracias! – keflavich

+1

Ok, pero usted específicamente pidió una solución sin clases, y es por eso que se me ocurrió esto. Usar una sentencia predeterminada es otra idea que tuve, por cierto. –

1

No se puede hacer un nombre simple, como x, para evaluar realmente perezosamente. Un nombre es solo una entrada en una tabla hash (por ejemplo, en la que locals() o globals() devuelve). A menos que aplique un parche a los métodos de acceso de estas tablas del sistema, no puede adjuntar la ejecución de su código a una resolución de nombre simple.

Pero puede envolver funciones en contenedores de caché de diferentes maneras. Esta es una manera 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 

Ésta es una manera sin clases:

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 

En el mundo real, se le añade un poco de lógica para mantener la memoria caché de un tamaño razonable, posiblemente usando un algoritmo LRU .

+0

¿Qué es un algoritmo LRU? Me gusta su enfoque del problema, es similar al anterior de Gringo, pero tal vez más elegante en algunos casos, pero mi problema está bastante bien abordado por los atributos de clase. En realidad, no necesito una variable global "x" para evaluarla con pereza; Necesito atributos de clase para ser evaluado perezosamente. Sin embargo, su solución es buena para el primer caso. – keflavich

+0

Por curiosidad, sin embargo ... ¿los locales() dict/hashtable son una clase que podría tener un atributo anulado? Creo que sería una idea terrible, pero ¿es posible en principio? – keflavich

+0

LRU = [Uso menos reciente] (http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used): una vez que crea o accede a una entrada de caché, la coloca al principio de la lista; la entrada utilizada menos recientemente se descarta si la lista crece demasiado tiempo. Ambos 'locals()' y 'globals()' devuelven un dict C-based con ranuras de método de solo lectura, sin posibilidad de anulación. – 9000

Cuestiones relacionadas