Certo, basta avere la vostra proprietà ubicata attributo un'istanza che viene restituito il successivo accesso:
class Foo(object):
_cached_bar = None
@property
def bar(self):
if not self._cached_bar:
self._cached_bar = self._get_expensive_bar_expression()
return self._cached_bar
Il descrittore property
è un descrittore di dati (implementa gli hook del descrittore __get__
, __set__
e __delete__
), quindi verrà invocato anche se esiste un attributo bar
nell'istanza, con il risultato finale che Python ignora tale attributo, quindi la necessità di testare per un separato attrib ute su ogni accesso.
È possibile scrivere il proprio descrittore che implementa solo __get__
, a quel punto Python utilizza un attributo sull'istanza sopra il descrittore se esiste:
class CachedProperty(object):
def __init__(self, func, name=None):
self.func = func
self.name = name if name is not None else func.__name__
self.__doc__ = func.__doc__
def __get__(self, instance, class_):
if instance is None:
return self
res = self.func(instance)
setattr(instance, self.name, res)
return res
class Foo(object):
@CachedProperty
def bar(self):
return self._get_expensive_bar_expression()
Se si preferisce un approccio __getattr__
(che ha qualcosa a che dicono per esso), che sarebbe:
class Foo(object):
def __getattr__(self, name):
if name == 'bar':
bar = self.bar = self._get_expensive_bar_expression()
return bar
return super(Foo, self).__getattr__(name)
accesso successivo sarà trovare l'attributo bar
sull'istanza e non verrà consultato __getattr__
.
Demo:
>>> class FooExpensive(object):
... def _get_expensive_bar_expression(self):
... print 'Doing something expensive'
... return 'Spam ham & eggs'
...
>>> class FooProperty(FooExpensive):
... _cached_bar = None
... @property
... def bar(self):
... if not self._cached_bar:
... self._cached_bar = self._get_expensive_bar_expression()
... return self._cached_bar
...
>>> f = FooProperty()
>>> f.bar
Doing something expensive
'Spam ham & eggs'
>>> f.bar
'Spam ham & eggs'
>>> vars(f)
{'_cached_bar': 'Spam ham & eggs'}
>>> class FooDescriptor(FooExpensive):
... bar = CachedProperty(FooExpensive._get_expensive_bar_expression, 'bar')
...
>>> f = FooDescriptor()
>>> f.bar
Doing something expensive
'Spam ham & eggs'
>>> f.bar
'Spam ham & eggs'
>>> vars(f)
{'bar': 'Spam ham & eggs'}
>>> class FooGetAttr(FooExpensive):
... def __getattr__(self, name):
... if name == 'bar':
... bar = self.bar = self._get_expensive_bar_expression()
... return bar
... return super(Foo, self).__getatt__(name)
...
>>> f = FooGetAttr()
>>> f.bar
Doing something expensive
'Spam ham & eggs'
>>> f.bar
'Spam ham & eggs'
>>> vars(f)
{'bar': 'Spam ham & eggs'}
Potete farlo automaticamente con descrittori: http://jeetworks.org/node/62 – schlamar
Werkzeug ha una migliore attuazione con ampia commenti: https://github.com/mitsuhiko/werkzeug/blob/10b4b8b6918a83712170fdaabd3ec61cf07f23ff/werkzeug/utils.py#L35 – schlamar
Vedi anche: [Python lazy property decorator] (http://stackoverflow.com/questions/3012421/python- lazy-proprietà-decorator). – detly