Skip to content

Commit

Permalink
bpo-44310: Add a FAQ entry for caching method calls (GH-26731)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhettinger authored Jun 17, 2021
1 parent f73377d commit 7f01f77
Showing 1 changed file with 97 additions and 0 deletions.
97 changes: 97 additions & 0 deletions Doc/faq/programming.rst
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 comments on commit 7f01f77

Please sign in to comment.