8000 bpo-44310: Add a FAQ entry for caching method calls by rhettinger · Pull Request #26731 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-44310: Add a FAQ entry for caching method calls #26731

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Jun 17, 2021
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bbd2da9
Merge pull request #1 from python/master
rhettinger Mar 16, 2021
74bdf1b
Merge branch 'master' of github.com:python/cpython
rhettinger Mar 22, 2021
6c53f1a
Merge branch 'master' of github.com:python/cpython
rhettinger Mar 22, 2021
a487c4f
.
rhettinger Mar 24, 2021
eb56423
.
rhettinger Mar 25, 2021
cc7ba06
.
rhettinger Mar 26, 2021
d024dd0
.
rhettinger Apr 22, 2021
b10f912
merge
rhettinger May 5, 2021
0958bf0
merge
rhettinger May 6, 2021
399afee
Merge branch 'main' of github.com:python/cpython
rhettinger May 8, 2021
daf9f13
Merge branch 'main' of github.com:python/cpython
rhettinger May 13, 2021
423c26e
Merge branch 'main' of github.com:python/cpython
rhettinger May 14, 2021
919d54d
Merge branch 'main' of github.com:python/cpython
rhettinger May 15, 2021
6666c42
Merge branch 'main' of github.com:python/cpython
rhettinger May 16, 2021
9fc71d6
Merge branch 'main' of github.com:python/cpython
rhettinger May 18, 2021
5f4f498
Merge branch 'main' of github.com:python/cpython
rhettinger May 21, 2021
a5aa352
Merge branch 'main' of github.com:python/cpython
rhettinger May 25, 2021
d1fceb2
Merge branch 'main' of github.com:python/cpython
rhettinger May 25, 2021
dd123c6
Merge branch 'main' of github.com:python/cpython
rhettinger May 26, 2021
9dc931c
Merge branch 'main' of github.com:python/cpython
rhettinger Jun 4, 2021
0492914
Merge branch 'main' of github.com:python/cpython
rhettinger Jun 5, 2021
f051f02
Merge branch 'main' of github.com:rhettinger/cpython
rhettinger Jun 14, 2021
1c2b85c
bpo-44310: Add a FAQ entry for caching method calls
rhettinger Jun 15, 2021
3aeef24
Fix markup
rhettinger Jun 15, 2021
16ef559
Move docstring to the public method
rhettinger Jun 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,103 @@ For example, here is the implementation of
return True
return False

How do I cache method calls?
----------------------------

The two principal tools for caching methods are
:func:`functools.cached_property` and :func:`functools.lru_cache`. The
former stores results at the instance level and the latter at the class
level.

The *cached_property* approach only works with methods that do not take
any arguments. It does not create a reference to the instance. The
cached method result will be kept only as long as the instance is alive.

The advantage is that when an instance is not longer used, the cached
method result will be released right away. The disadvantage is that if
instances accumulate, so too will the accumulated method results. They
can grow without bound.

The *lru_cache* approach works with methods that have hashable
arguments. It creates a reference to the instance unless special
efforts are made to pass in weak references.

The advantage of the least recently used algorithm is that the cache is
bounded by the specified *maxsize*. The disadvantage is that instances
are kept alive until they age out of the cache or until the cache is
cleared.

To avoid keeping an instance alive, it can be wrapped a weak reference
proxy. That allows an instance to be freed prior aging out of the LRU
cache. That said, the weak reference technique is rarely needed. It is
only helpful when the instances hold large amounts of data and the
normal aging-out process isn't fast enough. And even though the
instance is released early, the cache still keeps references to the
other method arguments and to the result of the method call.

This example shows the various techniques::

class Weather:
"Lookup weather information on a government website"

def __init__(self, station_id):
self._station_id = station_id
# The _station_id is private and immutable

def current_temperature(self):
"Latest hourly observation"
# Do not cache this because old results
# can be out of date.

@cached_property
def location(self):
"Return the longitude/latitude coordinates of the station"
# Result only depends on the station_id

@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='mm'):
"Rainfall on a given date"
# Depends on the station_id, date, and units.

def climate(self, category='average_temperature'):
"List of daily average temperatures for a full year"
return self._climate(weakref.proxy(self), category)

@staticmethod
@lru_cache(maxsize=10)
def _climate(self_proxy, category):
# Depends on a weak reference to the instance
# and on the category parameter.

The above example assumes that the *station_id* never changes. If the
relevant instance attributes are mutable, the *cached_property* approach
can't be made to work because it cannot detect changes to the
attributes.

The *lru_cache* approach can be made to work, but the class needs to define the
*__eq__* and *__hash__* methods so the cache can detect relevant attribute
updates::

class Weather:
"Example with a mutable station identifier"

def __init__(self, station_id):
self.station_id = station_id

def change_station(self, station_id):
self.station_id = station_id

def __eq__(self, other):
return self.station_id == other.station_id

def __hash__(self):
return hash(self.station_id)

@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='cm'):
'Rainfall on a given date'
# Depends on the station_id, date, and units.


Modules
=======
Expand Down
0