Why doesn't the value of `monopoly.boardwalk` change? Because it's a **cached property**!
+What if I want to use a cache that times out?
+-------------------------------------------------
+
+Just import the `timed_cached_property`:
+
+.. code-block:: python
+
+ from cached_property import timed_cached_property
+
+ class Monopoly(object):
+
+ def __init__(self):
+ self.boardwalk_price = 500
+
+ # Times out in 5 minutes
+ @timed_cached_property(ttl=300)
+ def boardwalk(self):
+ # Sometimes the market crashes and prices drop back down to their
+ # original value.
+ self.boardwalk_price += 50
+ return self.boardwalk_price
+
Credits
--------
__version__ = '0.1.4'
__license__ = 'BSD'
+import time
+
class cached_property(object):
""" A property that is only computed once per instance and then replaces
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value
+
+
+class timed_cached_property(object):
+ '''Decorator for read-only properties evaluated only once within TTL period.
+
+ It can be used to created a cached property like this::
+
+ import random
+
+ # the class containing the property must be a new-style class
+ class MyClass(object):
+ # create property whose value is cached for ten minutes
+ @cached_property(ttl=600)
+ def randint(self):
+ # will only be evaluated every 10 min. at maximum.
+ return random.randint(0, 100)
+
+ The value is cached in the '_cache' attribute of the object instance that
+ has the property getter method wrapped by this decorator. The '_cache'
+ attribute value is a dictionary which has a key for every property of the
+ object which is wrapped by this decorator. Each entry in the cache is
+ created only when the property is accessed for the first time and is a
+ two-element tuple with the last computed property value and the last time
+ it was updated in seconds since the epoch.
+
+ The default time-to-live (TTL) is 0, which also means the cache never expires.
+
+ To expire a cached property value manually just do::
+
+ del instance._cache[<property name>]
+
+ © 2011 Christopher Arndt, MIT License
+ source: https://wiki.python.org/moin/PythonDecoratorLibrary#Cached_Properties
+
+ '''
+ def __init__(self, ttl=0):
+ self.ttl = ttl
+
+ def __call__(self, fget, doc=None):
+ self.fget = fget
+ self.__doc__ = doc or fget.__doc__
+ self.__name__ = fget.__name__
+ self.__module__ = fget.__module__
+ return self
+
+ def __get__(self, inst, owner):
+ now = time.time()
+ try:
+ value, last_update = inst._cache[self.__name__]
+ if self.ttl > 0 and now - last_update > self.ttl:
+ raise AttributeError
+ except (KeyError, AttributeError):
+ value = self.fget(inst)
+ try:
+ cache = inst._cache
+ except AttributeError:
+ cache = inst._cache = {}
+ cache[self.__name__] = (value, now)
+ return value
import unittest
-from cached_property import cached_property
+from cached_property import cached_property, timed_cached_property
class TestCachedProperty(unittest.TestCase):
self.assertEqual(c.add_cached, 1)
+class TestTimedCachedProperty(unittest.TestCase):
+
+ def test_normal_cached_property(self):
+
+ class Check(object):
+
+ def __init__(self):
+ self.total1 = 0
+ self.total2 = 0
+
+ @property
+ def add_control(self):
+ self.total1 += 1
+ return self.total1
+
+ @timed_cached_property(ttl=1)
+ def add_cached(self):
+ self.total2 += 1
+ return self.total2
+
+ c = Check()
+
+ # The control shows that we can continue to add 1.
+ self.assertEqual(c.add_control, 1)
+ self.assertEqual(c.add_control, 2)
+
+ # The cached version demonstrates how nothing new is added
+ self.assertEqual(c.add_cached, 1)
+ self.assertEqual(c.add_cached, 1)
+
if __name__ == '__main__':
unittest.main()