2011-03-14 12 views
6

¿Hay una manera Pythonic de encapsular una llamada de función perezosa, mediante la cual al usar por primera vez la función f(), llama a una función previamente enlazada g(Z) y en las llamadas sucesivas f() devuelve un valor en caché?Python perez evaluador

Tenga en cuenta que la memorización puede no ser la opción perfecta.

que tengo:

f = g(Z) 
if x: 
    return 5 
elif y: 
    return f 
elif z: 
    return h(f) 

funciona el código, pero quiero reestructurarlo de manera que sólo se le llama g(Z) si se utiliza el valor. No quiero cambiar la definición de g(...), y Z es un poco grande para caché.

EDIT: Supuse que f debería ser una función, pero puede no ser el caso.

+2

No estoy seguro de que esto es lo que generalmente se entiende por * lazy *. Más seguro llamarlo * almacenamiento en caché * o * memoización *. –

+0

@John Y tiene razón: "evaluación diferida" se refiere a no computar el resultado de expresiones que no afectarán el resultado de la expresión que contiene, ej. en 'f() y g()', una evaluación diferida no llamará 'g()' si 'f()' es 'False'. Esta pregunta no es sobre eso. – detly

+0

es memoialización cuando hay argumentos de función. De lo contrario, es solo una llamada de función perezosa. –

Respuesta

6

Estoy un poco confundido si busca almacenamiento en caché o evaluación perezosa. Para este último, consulte el módulo lazy.py by Alberto Bertogli.

+0

Estoy bastante seguro de que esto es exactamente lo que estaba buscando. –

2

Trate de usar este decorador:

class Memoize: 
    def __init__ (self, f): 
     self.f = f 
     self.mem = {} 
    def __call__ (self, *args, **kwargs): 
     if (args, str(kwargs)) in self.mem: 
      return self.mem[args, str(kwargs)] 
     else: 
      tmp = self.f(*args, **kwargs) 
      self.mem[args, str(kwargs)] = tmp 
      return tmp 

(extraído de enlace muerto: http://snippets.dzone.com/posts/show/4840/https://web.archive.org/web/20081026130601/http://snippets.dzone.com/posts/show/4840) (encontrado aquí: Is there a decorator to simply cache function return values? por Alex Martelli)

EDIT: Aquí hay otro en forma de propiedades (usando __get__) http://code.activestate.com/recipes/363602/

1

Hay bastantes decoradores para memoria:

http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize http://code.activestate.com/recipes/498110-memoize-decorator-with-o1-length-limited-lru-cache/ http://code.activestate.com/recipes/496879-memoize-decorator-function-with-cache-size-limit/

El subir con una solución completamente general es más difícil de lo que piensas Por ejemplo, debe tener cuidado con los argumentos de la función que no se puede procesar y debe asegurarse de que la caché no crezca demasiado.

Si realmente está buscando una llamada de función diferida (una en la que la función solo se evalúa realmente si y cuando se necesita el valor), probablemente podría usar generadores para eso.

EDITAR: Así que supongo que lo que realmente quieres es una evaluación perezosa después de todo. Aquí hay una biblioteca que es probablemente lo que usted está buscando:

http://pypi.python.org/pypi/lazypy/0.5

1

Usted puede emplear un decorador de caché, deja ver un ejemplo

from functools import wraps 

class FuncCache(object): 
    def __init__(self): 
     self.cache = {} 

    def __call__(self, func): 
     @wraps(func) 
     def callee(*args, **kwargs): 
      key = (args, str(kwargs)) 
      # see is there already result in cache 
      if key in self.cache: 
       result = self.cache.get(key) 
      else: 
       result = func(*args, **kwargs) 
       self.cache[key] = result 
      return result 
     return callee 

con el decorador caché, aquí se puede escribir

my_cache = FuncCache() 

@my_cache 
def foo(n): 
    """Expensive calculation 

    """ 
    sum = 0 
    for i in xrange(n): 
     sum += i 
    print 'called foo with result', sum 
    return sum 

print foo(10000) 
print foo(10000) 
print foo(1234) 

Como se puede ver en la salida

called foo with result 49995000 
49995000 
49995000 

Se llamará al foo solo una vez. No tiene que cambiar ninguna línea de su función foo. Ese es el poder de los decoradores.

0

Incluso después de su edición, y la serie de comentarios con detly, todavía no entiendo realmente.En su primera oración, dice que se supone que la primera llamada a f() llama a g(), pero posteriormente devuelve valores en caché. Pero luego en sus comentarios, dice "g() no se llama sin importar qué" (énfasis mío). No estoy seguro de lo que estás negando: ¿Estás diciendo que g() debería llamarse nunca (no tiene mucho sentido, ¿por qué g() existe?); o que se llame a g() , pero podría no (bueno, eso todavía contradice que se llame a g() en la primera llamada a f()). A continuación, da un fragmento que no involucra g() en absoluto, y realmente no se relaciona ni con la primera frase de su pregunta, ni con el hilo de comentario con detly.

En caso de ir editando de nuevo, aquí está el fragmento que estoy respondiendo:

tengo:

a = f(Z) 
if x: 
    return 5 
elif y: 
    return a 
elif z: 
    return h(a) 

funciona el código, pero quiero reestructurarlo de manera que f (Z) es solo llamado si se usa el valor. No deseo querer cambiar la definición de f (...), y Z es un poco grande para caché.

Si ese es realmente su pregunta, entonces la respuesta es simplemente

if x: 
    return 5 
elif y: 
    return f(Z) 
elif z: 
    return h(f(Z)) 

Esa es la forma de lograr "f (Z) sólo se llama si se utiliza el valor".

No entiendo completamente "Z es un poco grande para caché". Si quiere decir que habrá demasiados valores diferentes de Z en el curso de la ejecución del programa, que la memorización es inútil, entonces tal vez tenga que recurrir a precalcular todos los valores de f (Z) y simplemente buscarlos en el tiempo de ejecución. Si no puede hacer esto (porque no puede conocer los valores de Z que encontrará su programa), entonces ha vuelto a la memorización. Si todavía es demasiado lento, entonces tu única opción real es usar algo más rápido que Python (prueba Psyco, Cython, ShedSkin o el módulo C codificado a mano).

+0

Alternativamente, si la expresión 'f (Z)' es más larga que eso en la práctica, solo tiene dos declaraciones 'if' separadas, con la segunda anidada dentro de la cláusula' else' de la primera. – ncoghlan

+0

Estoy tratando de escribir el código de la manera en que lo creo, por lo que quiero que f (Z) se vincule con algún nombre antes de ingresar el 'if'. Entonces, si necesito el resultado de f (Z), puedo consultar su longitud, usar su valor, etc. y sé que solo se creará si es necesario. –

1

Here 'Sa bastante breve perezoso-decorador, aunque le falta el uso de @functools.wraps (y en realidad devuelve una instancia de Lazy más algunas otras trampas potenciales):

class Lazy(object): 
    def __init__(self, calculate_function): 
     self._calculate = calculate_function 

    def __get__(self, obj, _=None): 
     if obj is None: 
      return self 
     value = self._calculate(obj) 
     setattr(obj, self._calculate.func_name, value) 
     return value 


# Sample use: 

class SomeClass(object): 

    @Lazy 
    def someprop(self): 
     print 'Actually calculating value' 
     return 13 


o = SomeClass() 
o.someprop 
o.someprop