summaryrefslogtreecommitdiffstats
path: root/Doc/faq/programming.rst
diff options
context:
space:
mode:
authorRaymond Hettinger <rhettinger@users.noreply.github.com>2021-06-17 20:39:42 (GMT)
committerGitHub <noreply@github.com>2021-06-17 20:39:42 (GMT)
commit7f01f77f8fabcfd7ddb5d99f12d6fc99af9af384 (patch)
treed3c72345f76809642accdc3eadf13d93553db7d1 /Doc/faq/programming.rst
parentf73377d57c5272390de63cccc3c292c44689310a (diff)
downloadcpython-7f01f77f8fabcfd7ddb5d99f12d6fc99af9af384.zip
cpython-7f01f77f8fabcfd7ddb5d99f12d6fc99af9af384.tar.gz
cpython-7f01f77f8fabcfd7ddb5d99f12d6fc99af9af384.tar.bz2
bpo-44310: Add a FAQ entry for caching method calls (GH-26731)
Diffstat (limited to 'Doc/faq/programming.rst')
-rw-r--r--Doc/faq/programming.rst97
1 files changed, 97 insertions, 0 deletions
diff --git a/Doc/faq/programming.rst b/Doc/faq/programming.rst
index 921357d..a519275 100644
--- a/Doc/faq/programming.rst
+++ b/Doc/faq/programming.rst
@@ -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
=======