Bump for 1.1.0
[cached-property.git] / README.rst
1 ===============================
2 cached-property
3 ===============================
4
5 .. image:: https://badge.fury.io/py/cached-property.png
6 :target: http://badge.fury.io/py/cached-property
7
8 .. image:: https://travis-ci.org/pydanny/cached-property.png?branch=master
9 :target: https://travis-ci.org/pydanny/cached-property
10
11 .. image:: https://pypip.in/d/cached-property/badge.png
12 :target: https://pypi.python.org/pypi/cached-property
13
14
15 A cached-property for decorating methods in classes.
16
17 Why?
18 -----
19
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.
23
24 How to use it
25 --------------
26
27 Let's define a class with an expensive property. Every time you stay there the
28 price goes up by $50!
29
30 .. code-block:: python
31
32 class Monopoly(object):
33
34 def __init__(self):
35 self.boardwalk_price = 500
36
37 @property
38 def boardwalk(self):
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
43
44 Now run it:
45
46 .. code-block:: python
47
48 >>> monopoly = Monopoly()
49 >>> monopoly.boardwalk
50 550
51 >>> monopoly.boardwalk
52 600
53
54 Let's convert the boardwalk property into a ``cached_property``.
55
56 .. code-block:: python
57
58 from cached_property import cached_property
59
60 class Monopoly(object):
61
62 def __init__(self):
63 self.boardwalk_price = 500
64
65 @cached_property
66 def boardwalk(self):
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
71
72 Now when we run it the price stays at $550.
73
74 .. code-block:: python
75
76 >>> monopoly = Monopoly()
77 >>> monopoly.boardwalk
78 550
79 >>> monopoly.boardwalk
80 550
81 >>> monopoly.boardwalk
82 550
83
84 Why doesn't the value of ``monopoly.boardwalk`` change? Because it's a **cached property**!
85
86 Invalidating the Cache
87 ----------------------
88
89 Results of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:
90
91 .. code-block:: python
92
93 >>> monopoly = Monopoly()
94 >>> monopoly.boardwalk
95 550
96 >>> monopoly.boardwalk
97 550
98 >>> # invalidate the cache
99 >>> del monopoly.boardwalk
100 >>> # request the boardwalk property again
101 >>> monopoly.boardwalk
102 600
103 >>> monopoly.boardwalk
104 600
105
106 Working with Threads
107 ---------------------
108
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``:
112
113 .. code-block:: python
114
115 import threading
116
117 from cached_property import threaded_cached_property
118
119 class Monopoly(object):
120
121 def __init__(self):
122 self.boardwalk_price = 500
123 self.lock = threading.Lock()
124
125 @threaded_cached_property
126 def boardwalk(self):
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."""
130
131 sleep(1)
132 # Need to guard this since += isn't atomic.
133 with self.lock:
134 self.boardwalk_price += 50
135 return self.boardwalk_price
136
137 Now use it:
138
139 .. code-block:: python
140
141 >>> from threading import Thread
142 >>> from monopoly import Monopoly
143 >>> monopoly = Monopoly()
144 >>> threads = []
145 >>> for x in range(10):
146 >>> thread = Thread(target=lambda: monopoly.boardwalk)
147 >>> thread.start()
148 >>> threads.append(thread)
149
150 >>> for thread in threads:
151 >>> thread.join()
152
153 >>> self.assertEqual(m.boardwalk, 550)
154
155
156 Timing out the cache
157 --------------------
158
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``.
161
162 .. code-block:: python
163
164 import random
165 from cached_property import cached_property_with_ttl
166
167 class Monopoly(object):
168
169 @cached_property_with_ttl(ttl=5) # cache invalidates after 5 seconds
170 def dice(self):
171 # I dare the reader to implement a game using this method of 'rolling dice'.
172 return random.randint(2,12)
173
174 Now use it:
175
176 .. code-block:: python
177
178 >>> monopoly = Monopoly()
179 >>> monopoly.dice
180 10
181 >>> monopoly.dice
182 10
183 >>> from time import sleep
184 >>> sleep(6) # Sleeps long enough to expire the cache
185 >>> monopoly.dice
186 3
187 >>> monopoly.dice
188 3
189 >>> # This cache clearing does not always work, see note below.
190 >>> del monopoly['dice']
191 >>> monopoly.dice
192 6
193
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.
196
197 Credits
198 --------
199
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
205
206 .. _`@audreyr`: https://github.com/audreyr
207 .. _`cookiecutter`: https://github.com/audreyr/cookiecutter