Lazy Properties & a Nifty Decorator
I sometimes use properties as a form of cache. You see this in Brubeck with current_user and current_userprofile. The idea is that if you don’t need access to the current user, the message handler won’t attempt to load it. If you do need it, the first time you try to access it will trigger the loading mechanism and off you go.
HT Tornado - I originally saw this technique in Tornado’s source code.
Basically, this is a lazy loading technique for attributes that people might use. However, if you do this frequently, as I’m doing right now to cache the compilation of WSDL files, you find yourself staring at a lot of boiler-plate code. Boooooooo.
I was mentioning this to my friend Alan and he asked why it couldn’t be done with a decorator. Well… He’s totally right. It can be.
A quick google turned up this stackoverflow link, which had the answer. I made a slight adjustment, but the general logic is still the same.
def lazyproperty(method):
attr_name = '_' + method.__name__
@property
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, method(self))
return getattr(self, attr_name)
return _lazyprop
The basic idea is that this decorator will create an attribute with the same name as the function you’re decorating. This attribute will cache the value returned by your function.
Example
Below we define a class called Foo which has a function called foo. We want foo to be our property.
Notice that foo() has a 5 second sleep in it. This will simulate something like loading a value from a database.
>>> class Foo(object):
... @lazyprop
... def foo(self):
... print 'Calculating foo, which might take a long time'
... time.sleep(5)
... return 'foo'
...
Don’t forget to import time.
>>> import time
OK. We’ll instantiate Foo and see what happens when we access our property foo. Before we do that, though, we’ll check the value of __dict__ to show that there are no cached values. After accessing the property we will see that self._foo exists, serving as our cache.
>>> f = Foo()
>>> f.__dict__
{}
>>> f.foo
Calculating foo, which might take a long time
'foo'
>>> f.foo
'foo'
>>> f.__dict__
{'_foo': 'foo'}
I really like how clean this approach is. Some folks find the use of Python properties less explicit than they’d prefer. If you have an opinion, please share it in the comments.
Updates!
Thanks @whitmo for informing me that the Pyramid project has a similar decorator: click here.