1 ===============================
3 ===============================
5 .. image:: https://img.shields.io/pypi/v/cached-property.svg
6 :target: https://pypi.python.org/pypi/cached-property
8 .. image:: https://img.shields.io/travis/pydanny/cached-property/master.svg
9 :target: https://travis-ci.org/pydanny/cached-property
11 .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
12 :target: https://github.com/ambv/black
13 :alt: Code style: black
16 A decorator for caching properties in classes.
21 * Makes caching of time or computational expensive properties quick and easy.
22 * Because I got tired of copy/pasting this code from non-web project to non-web project.
23 * I needed something really simple that worked in Python 2 and 3.
28 Let's define a class with an expensive property. Every time you stay there the
31 .. code-block:: python
33 class Monopoly(object):
36 self.boardwalk_price = 500
40 # In reality, this might represent a database call or time
41 # intensive task like calling a third-party API.
42 self.boardwalk_price += 50
43 return self.boardwalk_price
47 .. code-block:: python
49 >>> monopoly = Monopoly()
50 >>> monopoly.boardwalk
52 >>> monopoly.boardwalk
55 Let's convert the boardwalk property into a ``cached_property``.
57 .. code-block:: python
59 from cached_property import cached_property
61 class Monopoly(object):
64 self.boardwalk_price = 500
68 # Again, this is a silly example. Don't worry about it, this is
69 # just an example for clarity.
70 self.boardwalk_price += 50
71 return self.boardwalk_price
73 Now when we run it the price stays at $550.
75 .. code-block:: python
77 >>> monopoly = Monopoly()
78 >>> monopoly.boardwalk
80 >>> monopoly.boardwalk
82 >>> monopoly.boardwalk
85 Why doesn't the value of ``monopoly.boardwalk`` change? Because it's a **cached property**!
87 Invalidating the Cache
88 ----------------------
90 Results of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:
92 .. code-block:: python
94 >>> monopoly = Monopoly()
95 >>> monopoly.boardwalk
97 >>> monopoly.boardwalk
99 >>> # invalidate the cache
100 >>> del monopoly.__dict__['boardwalk']
101 >>> # request the boardwalk property again
102 >>> monopoly.boardwalk
104 >>> monopoly.boardwalk
108 ---------------------
110 What if a whole bunch of people want to stay at Boardwalk all at once? This means using threads, which
111 unfortunately causes problems with the standard ``cached_property``. In this case, switch to using the
112 ``threaded_cached_property``:
114 .. code-block:: python
116 from cached_property import threaded_cached_property
118 class Monopoly(object):
121 self.boardwalk_price = 500
123 @threaded_cached_property
125 """threaded_cached_property is really nice for when no one waits
126 for other people to finish their turn and rudely start rolling
127 dice and moving their pieces."""
130 self.boardwalk_price += 50
131 return self.boardwalk_price
135 .. code-block:: python
137 >>> from threading import Thread
138 >>> from monopoly import Monopoly
139 >>> monopoly = Monopoly()
141 >>> for x in range(10):
142 >>> thread = Thread(target=lambda: monopoly.boardwalk)
144 >>> threads.append(thread)
146 >>> for thread in threads:
149 >>> self.assertEqual(m.boardwalk, 550)
152 Working with async/await (Python 3.5+)
153 --------------------------------------
155 The cached property can be async, in which case you have to use await
156 as usual to get the value. Because of the caching, the value is only
157 computed once and then cached:
159 .. code-block:: python
161 from cached_property import cached_property
163 class Monopoly(object):
166 self.boardwalk_price = 500
169 async def boardwalk(self):
170 self.boardwalk_price += 50
171 return self.boardwalk_price
175 .. code-block:: python
177 >>> async def print_boardwalk():
178 ... monopoly = Monopoly()
179 ... print(await monopoly.boardwalk)
180 ... print(await monopoly.boardwalk)
181 ... print(await monopoly.boardwalk)
183 >>> asyncio.get_event_loop().run_until_complete(print_boardwalk())
188 Note that this does not work with threading either, most asyncio
189 objects are not thread-safe. And if you run separate event loops in
190 each thread, the cached version will most likely have the wrong event
191 loop. To summarize, either use cooperative multitasking (event loop)
192 or threading, but not both at the same time.
198 Sometimes you want the price of things to reset after a time. Use the ``ttl``
199 versions of ``cached_property`` and ``threaded_cached_property``.
201 .. code-block:: python
204 from cached_property import cached_property_with_ttl
206 class Monopoly(object):
208 @cached_property_with_ttl(ttl=5) # cache invalidates after 5 seconds
210 # I dare the reader to implement a game using this method of 'rolling dice'.
211 return random.randint(2,12)
215 .. code-block:: python
217 >>> monopoly = Monopoly()
222 >>> from time import sleep
223 >>> sleep(6) # Sleeps long enough to expire the cache
229 **Note:** The ``ttl`` tools do not reliably allow the clearing of the cache. This
230 is why they are broken out into seperate tools. See https://github.com/pydanny/cached-property/issues/16.
235 * Pip, Django, Werkzueg, Bottle, Pyramid, and Zope for having their own implementations. This package originally used an implementation that matched the Bottle version.
236 * Reinout Van Rees for pointing out the `cached_property` decorator to me.
237 * My awesome wife `@audreyr`_ who created `cookiecutter`_, which meant rolling this out took me just 15 minutes.
238 * @tinche for pointing out the threading issue and providing a solution.
239 * @bcho for providing the time-to-expire feature
241 .. _`@audreyr`: https://github.com/audreyr
242 .. _`cookiecutter`: https://github.com/audreyr/cookiecutter
245 ---------------------------
247 This project is maintained by volunteers. Support their efforts by spreading the word about:
249 .. image:: https://cdn.shopify.com/s/files/1/0304/6901/t/2/assets/logo.png?8399580890922549623
250 :name: Two Scoops Press
252 :alt: Two Scoops Press
253 :target: https://www.twoscoopspress.com