1 ===============================
3 ===============================
5 .. image:: https://badge.fury.io/py/cached-property.png
6 :target: http://badge.fury.io/py/cached-property
8 .. image:: https://travis-ci.org/pydanny/cached-property.png?branch=master
9 :target: https://travis-ci.org/pydanny/cached-property
11 .. image:: https://pypip.in/d/cached-property/badge.png
12 :target: https://pypi.python.org/pypi/cached-property
15 A cached-property for decorating methods in classes.
20 * Makes caching of time or computational expensive properties quick and easy.
21 * Because I got tired of copy/pasting this code from non-web project to non-web project.
22 * I needed something really simple that worked in Python 2 and 3.
27 Let's define a class with an expensive property. Every time you stay there the
30 .. code-block:: python
32 class Monopoly(object):
35 self.boardwalk_price = 500
39 # In reality, this might represent a database call or time
40 # intensive task like calling a third-party API.
41 self.boardwalk_price += 50
42 return self.boardwalk_price
46 .. code-block:: python
48 >>> monopoly = Monopoly()
49 >>> monopoly.boardwalk
51 >>> monopoly.boardwalk
54 Let's convert the boardwalk property into a ``cached_property``.
56 .. code-block:: python
58 from cached_property import cached_property
60 class Monopoly(object):
63 self.boardwalk_price = 500
67 # Again, this is a silly example. Don't worry about it, this is
68 # just an example for clarity.
69 self.boardwalk_price += 50
70 return self.boardwalk_price
72 Now when we run it the price stays at $550.
74 .. code-block:: python
76 >>> monopoly = Monopoly()
77 >>> monopoly.boardwalk
79 >>> monopoly.boardwalk
81 >>> monopoly.boardwalk
84 Why doesn't the value of ``monopoly.boardwalk`` change? Because it's a **cached property**!
86 Invalidating the Cache
87 ----------------------
89 Results of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:
91 .. code-block:: python
93 >>> monopoly = Monopoly()
94 >>> monopoly.boardwalk
96 >>> monopoly.boardwalk
98 >>> # invalidate the cache
99 >>> del monopoly.boardwalk
100 >>> # request the boardwalk property again
101 >>> monopoly.boardwalk
103 >>> monopoly.boardwalk
107 ---------------------
109 What if a whole bunch of people want to stay at Boardwalk all at once? This means using threads, which
110 unfortunately causes problems with the standard ``cached_property``. In this case, switch to using the
111 ``threaded_cached_property``:
113 .. code-block:: python
117 from cached_property import threaded_cached_property
119 class Monopoly(object):
122 self.boardwalk_price = 500
123 self.lock = threading.Lock()
125 @threaded_cached_property
127 """threaded_cached_property is really nice for when no one waits
128 for other people to finish their turn and rudely start rolling
129 dice and moving their pieces."""
132 # Need to guard this since += isn't atomic.
134 self.boardwalk_price += 50
135 return self.boardwalk_price
139 .. code-block:: python
141 >>> from threading import Thread
142 >>> from monopoly import Monopoly
143 >>> monopoly = Monopoly()
145 >>> for x in range(10):
146 >>> thread = Thread(target=lambda: monopoly.boardwalk)
148 >>> threads.append(thread)
150 >>> for thread in threads:
153 >>> self.assertEqual(m.boardwalk, 550)
159 Sometimes you want the price of things to reset after a time. Use the ``ttl``
160 versions of ``cached_property`` and ``threaded_cached_property``.
162 .. code-block:: python
165 from cached_property import cached_property_with_ttl
167 class Monopoly(object):
169 @cached_property_with_ttl(ttl=5) # cache invalidates after 5 seconds
171 # I dare the reader to implement a game using this method of 'rolling dice'.
172 return random.randint(2,12)
176 .. code-block:: python
178 >>> monopoly = Monopoly()
183 >>> from time import sleep
184 >>> sleep(6) # Sleeps long enough to expire the cache
189 >>> # This cache clearing does not always work, see note below.
190 >>> del monopoly['dice']
194 **Note:** The ``ttl`` tools do not reliably allow the clearing of the cache. This
195 is why they are broken out into seperate tools. See https://github.com/pydanny/cached-property/issues/16.
200 * Pip, Django, Werkzueg, Bottle, Pyramid, and Zope for having their own implementations. This package uses an implementation that matches the Bottle version.
201 * Reinout Van Rees for pointing out the `cached_property` decorator to me.
202 * My awesome wife `@audreyr`_ who created `cookiecutter`_, which meant rolling this out took me just 15 minutes.
203 * @tinche for pointing out the threading issue and providing a solution.
204 * @bcho for providing the time-to-expire feature
206 .. _`@audreyr`: https://github.com/audreyr
207 .. _`cookiecutter`: https://github.com/audreyr/cookiecutter