2011-06-21 10 views
15

Mi pregunta es ¿Son los siguientes dos piezas de código ejecuta la misma por el intérprete:python: ¿los campos de propiedad se almacenan en caché automáticamente?

class A(object): 
    def __init__(self): 
    self.__x = None 

    @property 
    def x(self): 
    if not self.__x: 
     self.__x = ... #some complicated action 
    return self.__x 

y la mucho más simple:

class A(object): 
    @property 
    def x(self): 
     return ... #some complicated action 

Es decir, es el intérprete lo suficientemente inteligente como para almacenar en caché la propiedad x ?

Mi suposición es que x no cambia - encontrando que es difícil , pero una vez que encuentre una vez que no hay razón para encontrarla de nuevo.

Respuesta

13

No, se llamará al captador cada vez que acceda a la propiedad.

17

No es necesario agregar un decorador memoize:

class memoized(object): 
    """Decorator that caches a function's return value each time it is called. 
    If called later with the same arguments, the cached value is returned, and 
    not re-evaluated. 
    """ 
    def __init__(self, func): 
     self.func = func 
     self.cache = {} 
    def __call__(self, *args): 
     try: 
     return self.cache[args] 
     except KeyError: 
     value = self.func(*args) 
     self.cache[args] = value 
     return value 
     except TypeError: 
     # uncachable -- for instance, passing a list as an argument. 
     # Better to not cache than to blow up entirely. 
     return self.func(*args) 
    def __repr__(self): 
     """Return the function's docstring.""" 
     return self.func.__doc__ 
    def __get__(self, obj, objtype): 
     """Support instance methods.""" 
     return functools.partial(self.__call__, obj) 

@memoized 
def fibonacci(n): 
    "Return the nth fibonacci number." 
    if n in (0, 1): 
     return n 
    return fibonacci(n-1) + fibonacci(n-2) 

print fibonacci(12) 
+6

decorador memoize es probablemente una exageración para una simple propiedad pitón. – SingleNegationElimination

+1

También tenga en cuenta que, mientras tanto, está el decorador ['functools.lru_cache (maxsize = 128, typed = False)'] (https://docs.python.org/3/library/functools.html#functools.lru_cache) (que también permite un parámetro 'maxsize = None') –

8

Propiedades no lo hacen automáticamente caché de sus valores de retorno. El getter (y setters) están destinados a ser llamados cada vez que se accede a la propiedad.

Sin embargo, Denis Otkidach ha escrito un decorador atributo de caché maravillosa (publicado en the Python Cookbook, 2nd edition y también originalmente en ActiveState bajo la PSF license) para este propósito:

class cache(object):  
    '''Computes attribute value and caches it in the instance. 
    Python Cookbook (Denis Otkidach) https://stackoverflow.com/users/168352/denis-otkidach 
    This decorator allows you to create a property which can be computed once and 
    accessed many times. Sort of like memoization. 

    ''' 
    def __init__(self, method, name=None): 
     # record the unbound-method and the name 
     self.method = method 
     self.name = name or method.__name__ 
     self.__doc__ = method.__doc__ 
    def __get__(self, inst, cls): 
     # self: <__main__.cache object at 0xb781340c> 
     # inst: <__main__.Foo object at 0xb781348c> 
     # cls: <class '__main__.Foo'>  
     if inst is None: 
      # instance attribute accessed on class, return self 
      # You get here if you write `Foo.bar` 
      return self 
     # compute, cache and return the instance's attribute value 
     result = self.method(inst) 
     # setattr redefines the instance's attribute so this doesn't get called again 
     setattr(inst, self.name, result) 
     return result 

Aquí es un ejemplo que demuestra su uso:

def demo_cache(): 
    class Foo(object): 
     @cache 
     def bar(self): 
      print 'Calculating self.bar' 
      return 42 
    foo=Foo() 
    print(foo.bar) 
    # Calculating self.bar 
    # 42 
    print(foo.bar)  
    # 42 
    foo.bar=1 
    print(foo.bar) 
    # 1 
    print(Foo.bar) 
    # __get__ called with inst = None 
    # <__main__.cache object at 0xb7709b4c> 

    # Deleting `foo.bar` from `foo.__dict__` re-exposes the property defined in `Foo`. 
    # Thus, calling `foo.bar` again recalculates the value again. 
    del foo.bar 
    print(foo.bar) 
    # Calculating self.bar 
    # 42 

demo_cache() 
2

El decorador de Denis Otkidach mencionado en la respuesta de @ unutbu fue publicado en O'Reilly's Python Cookbook. Desafortunadamente O'Reilly no especifica ninguna licencia para ejemplos de código, solo como permiso informal para reutilizar el código.

Si necesita un decorador de la propiedad en caché con una licencia liberal, puede usar Ken Seehof 's @cached_property de ActiveState code recipes. Está explícitamente publicado bajo el MIT license.

def cached_property(f): 
    """returns a cached property that is calculated by function f""" 
    def get(self): 
     try: 
      return self._property_cache[f] 
     except AttributeError: 
      self._property_cache = {} 
      x = self._property_cache[f] = f(self) 
      return x 
     except KeyError: 
      x = self._property_cache[f] = f(self) 
      return x 

    return property(get) 
+1

Además, si está trabajando en un proyecto de Django, puede importar' @ cached_property' from 'django.utils.functional'. –

7

Python 3.2 en adelante ofrece un decorador incorporado que se puede utilizar para crear un caché LRU:

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

Alternativamente, si usted está usando Frasco/Werkzeug, está el @cached_property decorador .

Para Django, tratar from django.utils.functional import cached_property

+0

También disponible en Django 'de django.utils.functional import cached_property' –

Cuestiones relacionadas