3 [![Github Actions status](https://github.com/pydanny/cached-property/workflows/Python%20package/badge.svg)](https://github.com/pydanny/cached-property/actions)
4 [![PyPI](https://img.shields.io/pypi/v/cached-property.svg)](https://pypi.python.org/pypi/cached-property)
5 [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
7 A decorator for caching properties in classes.
11 * Makes caching of time or computational expensive properties quick and easy.
12 * Because I got tired of copy/pasting this code from non-web project to non-web project.
13 * I needed something really simple that worked in Python 2 and 3.
17 Let's define a class with an expensive property. Every time you stay there the
21 class Monopoly(object):
24 self.boardwalk_price = 500
28 # In reality, this might represent a database call or time
29 # intensive task like calling a third-party API.
30 self.boardwalk_price += 50
31 return self.boardwalk_price
37 >>> monopoly = Monopoly()
38 >>> monopoly.boardwalk
40 >>> monopoly.boardwalk
44 Let's convert the boardwalk property into a `cached_property`.
47 from cached_property import cached_property
49 class Monopoly(object):
52 self.boardwalk_price = 500
56 # Again, this is a silly example. Don't worry about it, this is
57 # just an example for clarity.
58 self.boardwalk_price += 50
59 return self.boardwalk_price
62 Now when we run it the price stays at $550.
65 >>> monopoly = Monopoly()
66 >>> monopoly.boardwalk
68 >>> monopoly.boardwalk
70 >>> monopoly.boardwalk
74 Why doesn't the value of `monopoly.boardwalk` change? Because it's a **cached property**!
76 ## Invalidating the Cache
78 Results of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:
81 >>> monopoly = Monopoly()
82 >>> monopoly.boardwalk
84 >>> monopoly.boardwalk
86 >>> # invalidate the cache
87 >>> del monopoly.__dict__['boardwalk']
88 >>> # request the boardwalk property again
89 >>> monopoly.boardwalk
91 >>> monopoly.boardwalk
95 ## Working with Threads
97 What if a whole bunch of people want to stay at Boardwalk all at once? This means using threads, which
98 unfortunately causes problems with the standard `cached_property`. In this case, switch to using the
99 `threaded_cached_property`:
102 from cached_property import threaded_cached_property
104 class Monopoly(object):
107 self.boardwalk_price = 500
109 @threaded_cached_property
111 """threaded_cached_property is really nice for when no one waits
112 for other people to finish their turn and rudely start rolling
113 dice and moving their pieces."""
116 self.boardwalk_price += 50
117 return self.boardwalk_price
123 >>> from threading import Thread
124 >>> from monopoly import Monopoly
125 >>> monopoly = Monopoly()
127 >>> for x in range(10):
128 >>> thread = Thread(target=lambda: monopoly.boardwalk)
130 >>> threads.append(thread)
132 >>> for thread in threads:
135 >>> self.assertEqual(m.boardwalk, 550)
138 ## Working with async/await (Python 3.5+)
140 The cached property can be async, in which case you have to use await
141 as usual to get the value. Because of the caching, the value is only
142 computed once and then cached:
145 from cached_property import cached_property
147 class Monopoly(object):
150 self.boardwalk_price = 500
153 async def boardwalk(self):
154 self.boardwalk_price += 50
155 return self.boardwalk_price
161 >>> async def print_boardwalk():
162 ... monopoly = Monopoly()
163 ... print(await monopoly.boardwalk)
164 ... print(await monopoly.boardwalk)
165 ... print(await monopoly.boardwalk)
167 >>> asyncio.get_event_loop().run_until_complete(print_boardwalk())
173 Note that this does not work with threading either, most asyncio
174 objects are not thread-safe. And if you run separate event loops in
175 each thread, the cached version will most likely have the wrong event
176 loop. To summarize, either use cooperative multitasking (event loop)
177 or threading, but not both at the same time.
179 ## Timing out the cache
181 Sometimes you want the price of things to reset after a time. Use the `ttl`
182 versions of `cached_property` and `threaded_cached_property`.
186 from cached_property import cached_property_with_ttl
188 class Monopoly(object):
190 @cached_property_with_ttl(ttl=5) # cache invalidates after 5 seconds
192 # I dare the reader to implement a game using this method of 'rolling dice'.
193 return random.randint(2,12)
199 >>> monopoly = Monopoly()
204 >>> from time import sleep
205 >>> sleep(6) # Sleeps long enough to expire the cache
212 **Note:** The `ttl` tools do not reliably allow the clearing of the cache. This
213 is why they are broken out into seperate tools. See https://github.com/pydanny/cached-property/issues/16.
217 * Pip, Django, Werkzueg, Bottle, Pyramid, and Zope for having their own implementations. This package originally used an implementation that matched the Bottle version.
218 * Reinout Van Rees for pointing out the `cached_property` decorator to me.
219 * My awesome wife [@audreyfeldroy](https://github.com/audreyfeldroy) who created [`cookiecutter`](https://github.com/cookiecutter/cookiecutter), which meant rolling this out took me just 15 minutes.
220 * @tinche for pointing out the threading issue and providing a solution.
221 * @bcho for providing the time-to-expire feature