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://github.com/pydanny/cached-property/workflows/Python%20package/badge.svg
9 :target: https://github.com/pydanny/cached-property/actions
12 .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
13 :target: https://github.com/ambv/black
14 :alt: Code style: black
17 A decorator for caching properties in classes.
22 * Makes caching of time or computational expensive properties quick and easy.
23 * Because I got tired of copy/pasting this code from non-web project to non-web project.
24 * I needed something really simple that worked in Python 2 and 3.
29 Let's define a class with an expensive property. Every time you stay there the
32 .. code-block:: python
34 class Monopoly(object):
37 self.boardwalk_price = 500
41 # In reality, this might represent a database call or time
42 # intensive task like calling a third-party API.
43 self.boardwalk_price += 50
44 return self.boardwalk_price
48 .. code-block:: python
50 >>> monopoly = Monopoly()
51 >>> monopoly.boardwalk
53 >>> monopoly.boardwalk
56 Let's convert the boardwalk property into a ``cached_property``.
58 .. code-block:: python
60 from cached_property import cached_property
62 class Monopoly(object):
65 self.boardwalk_price = 500
69 # Again, this is a silly example. Don't worry about it, this is
70 # just an example for clarity.
71 self.boardwalk_price += 50
72 return self.boardwalk_price
74 Now when we run it the price stays at $550.
76 .. code-block:: python
78 >>> monopoly = Monopoly()
79 >>> monopoly.boardwalk
81 >>> monopoly.boardwalk
83 >>> monopoly.boardwalk
86 Why doesn't the value of ``monopoly.boardwalk`` change? Because it's a **cached property**!
88 Invalidating the Cache
89 ----------------------
91 Results of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:
93 .. code-block:: python
95 >>> monopoly = Monopoly()
96 >>> monopoly.boardwalk
98 >>> monopoly.boardwalk
100 >>> # invalidate the cache
101 >>> del monopoly.__dict__['boardwalk']
102 >>> # request the boardwalk property again
103 >>> monopoly.boardwalk
105 >>> monopoly.boardwalk
109 ---------------------
111 What if a whole bunch of people want to stay at Boardwalk all at once? This means using threads, which
112 unfortunately causes problems with the standard ``cached_property``. In this case, switch to using the
113 ``threaded_cached_property``:
115 .. code-block:: python
117 from cached_property import threaded_cached_property
119 class Monopoly(object):
122 self.boardwalk_price = 500
124 @threaded_cached_property
126 """threaded_cached_property is really nice for when no one waits
127 for other people to finish their turn and rudely start rolling
128 dice and moving their pieces."""
131 self.boardwalk_price += 50
132 return self.boardwalk_price
136 .. code-block:: python
138 >>> from threading import Thread
139 >>> from monopoly import Monopoly
140 >>> monopoly = Monopoly()
142 >>> for x in range(10):
143 >>> thread = Thread(target=lambda: monopoly.boardwalk)
145 >>> threads.append(thread)
147 >>> for thread in threads:
150 >>> self.assertEqual(m.boardwalk, 550)
153 Working with async/await (Python 3.5+)
154 --------------------------------------
156 The cached property can be async, in which case you have to use await
157 as usual to get the value. Because of the caching, the value is only
158 computed once and then cached:
160 .. code-block:: python
162 from cached_property import cached_property
164 class Monopoly(object):
167 self.boardwalk_price = 500
170 async def boardwalk(self):
171 self.boardwalk_price += 50
172 return self.boardwalk_price
176 .. code-block:: python
178 >>> async def print_boardwalk():
179 ... monopoly = Monopoly()
180 ... print(await monopoly.boardwalk)
181 ... print(await monopoly.boardwalk)
182 ... print(await monopoly.boardwalk)
184 >>> asyncio.get_event_loop().run_until_complete(print_boardwalk())
189 Note that this does not work with threading either, most asyncio
190 objects are not thread-safe. And if you run separate event loops in
191 each thread, the cached version will most likely have the wrong event
192 loop. To summarize, either use cooperative multitasking (event loop)
193 or threading, but not both at the same time.
199 Sometimes you want the price of things to reset after a time. Use the ``ttl``
200 versions of ``cached_property`` and ``threaded_cached_property``.
202 .. code-block:: python
205 from cached_property import cached_property_with_ttl
207 class Monopoly(object):
209 @cached_property_with_ttl(ttl=5) # cache invalidates after 5 seconds
211 # I dare the reader to implement a game using this method of 'rolling dice'.
212 return random.randint(2,12)
216 .. code-block:: python
218 >>> monopoly = Monopoly()
223 >>> from time import sleep
224 >>> sleep(6) # Sleeps long enough to expire the cache
230 **Note:** The ``ttl`` tools do not reliably allow the clearing of the cache. This
231 is why they are broken out into seperate tools. See https://github.com/pydanny/cached-property/issues/16.
236 * Pip, Django, Werkzueg, Bottle, Pyramid, and Zope for having their own implementations. This package originally used an implementation that matched the Bottle version.
237 * Reinout Van Rees for pointing out the `cached_property` decorator to me.
238 * My awesome wife `@audreyr`_ who created `cookiecutter`_, which meant rolling this out took me just 15 minutes.
239 * @tinche for pointing out the threading issue and providing a solution.
240 * @bcho for providing the time-to-expire feature
242 .. _`@audreyr`: https://github.com/audreyr
243 .. _`cookiecutter`: https://github.com/audreyr/cookiecutter
246 ---------------------------
248 This project is maintained by volunteers. Support their efforts by spreading the word about:
251 ++++++++++++++++++++++++++++++++++++
253 .. image:: https://cdn.shopify.com/s/files/1/0304/6901/products/AWoD-Front-5.5x8.5in_1024x1024@2x.jpg
254 :name: A Wedge of Django: Covers Django 3.x and Python 3.8
256 :alt: A Wedge of Django
257 :target: https://www.feldroy.com/products/django-crash-course
259 A Wedge of Django is a crash course for Django 3.3 and Python 3.8 is the best cheese-themed Django reference in the universe!