Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python memoising/deferred lookup property decorator

Recently I've gone through an existing code base containing many classes where instance attributes reflect values stored in a database. I've refactored a lot of these attributes to have their database lookups be deferred, ie. not be initialised in the constructor but only upon first read. These attributes do not change over the lifetime of the instance, but they're a real bottleneck to calculate that first time and only really accessed for special cases. Hence they can also be cached after they've been retrieved from the database (this therefore fits the definition of memoisation where the input is simply "no input").

I find myself typing the following snippet of code over and over again for various attributes across various classes:

class testA(object):    def __init__(self):     self._a = None     self._b = None    @property   def a(self):     if self._a is None:       # Calculate the attribute now       self._a = 7     return self._a    @property   def b(self):     #etc 

Is there an existing decorator to do this already in Python that I'm simply unaware of? Or, is there a reasonably simple way to define a decorator that does this?

I'm working under Python 2.5, but 2.6 answers might still be interesting if they are significantly different.

Note

This question was asked before Python included a lot of ready-made decorators for this. I have updated it only to correct terminology.

like image 664
detly Avatar asked Jun 10 '10 07:06

detly


1 Answers

Here is an example implementation of a lazy property decorator:

import functools  def lazyprop(fn):     attr_name = '_lazy_' + fn.__name__      @property     @functools.wraps(fn)     def _lazyprop(self):         if not hasattr(self, attr_name):             setattr(self, attr_name, fn(self))         return getattr(self, attr_name)      return _lazyprop   class Test(object):      @lazyprop     def a(self):         print 'generating "a"'         return range(5) 

Interactive session:

>>> t = Test() >>> t.__dict__ {} >>> t.a generating "a" [0, 1, 2, 3, 4] >>> t.__dict__ {'_lazy_a': [0, 1, 2, 3, 4]} >>> t.a [0, 1, 2, 3, 4] 
like image 96
Mike Boers Avatar answered Oct 01 '22 18:10

Mike Boers