Skip to content

Commit 7f01f77

Browse files
authored
bpo-44310: Add a FAQ entry for caching method calls (GH-26731)
1 parent f73377d commit 7f01f77

File tree

1 file changed

+97
-0
lines changed

1 file changed

+97
-0
lines changed

Doc/faq/programming.rst

+97
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,103 @@ For example, here is the implementation of
18261826
return True
18271827
return False
18281828

1829+
How do I cache method calls?
1830+
----------------------------
1831+
1832+
The two principal tools for caching methods are
1833+
:func:`functools.cached_property` and :func:`functools.lru_cache`. The
1834+
former stores results at the instance level and the latter at the class
1835+
level.
1836+
1837+
The *cached_property* approach only works with methods that do not take
1838+
any arguments. It does not create a reference to the instance. The
1839+
cached method result will be kept only as long as the instance is alive.
1840+
1841+
The advantage is that when an instance is not longer used, the cached
1842+
method result will be released right away. The disadvantage is that if
1843+
instances accumulate, so too will the accumulated method results. They
1844+
can grow without bound.
1845+
1846+
The *lru_cache* approach works with methods that have hashable
1847+
arguments. It creates a reference to the instance unless special
1848+
efforts are made to pass in weak references.
1849+
1850+
The advantage of the least recently used algorithm is that the cache is
1851+
bounded by the specified *maxsize*. The disadvantage is that instances
1852+
are kept alive until they age out of the cache or until the cache is
1853+
cleared.
1854+
1855+
To avoid keeping an instance alive, it can be wrapped a weak reference
1856+
proxy. That allows an instance to be freed prior aging out of the LRU
1857+
cache. That said, the weak reference technique is rarely needed. It is
1858+
only helpful when the instances hold large amounts of data and the
1859+
normal aging-out process isn't fast enough. And even though the
1860+
instance is released early, the cache still keeps references to the
1861+
other method arguments and to the result of the method call.
1862+
1863+
This example shows the various techniques::
1864+
1865+
class Weather:
1866+
"Lookup weather information on a government website"
1867+
1868+
def __init__(self, station_id):
1869+
self._station_id = station_id
1870+
# The _station_id is private and immutable
1871+
1872+
def current_temperature(self):
1873+
"Latest hourly observation"
1874+
# Do not cache this because old results
1875+
# can be out of date.
1876+
1877+
@cached_property
1878+
def location(self):
1879+
"Return the longitude/latitude coordinates of the station"
1880+
# Result only depends on the station_id
1881+
1882+
@lru_cache(maxsize=20)
1883+
def historic_rainfall(self, date, units='mm'):
1884+
"Rainfall on a given date"
1885+
# Depends on the station_id, date, and units.
1886+
1887+
def climate(self, category='average_temperature'):
1888+
"List of daily average temperatures for a full year"
1889+
return self._climate(weakref.proxy(self), category)
1890+
1891+
@staticmethod
1892+
@lru_cache(maxsize=10)
1893+
def _climate(self_proxy, category):
1894+
# Depends on a weak reference to the instance
1895+
# and on the category parameter.
1896+
1897+
The above example assumes that the *station_id* never changes. If the
1898+
relevant instance attributes are mutable, the *cached_property* approach
1899+
can't be made to work because it cannot detect changes to the
1900+
attributes.
1901+
1902+
The *lru_cache* approach can be made to work, but the class needs to define the
1903+
*__eq__* and *__hash__* methods so the cache can detect relevant attribute
1904+
updates::
1905+
1906+
class Weather:
1907+
"Example with a mutable station identifier"
1908+
1909+
def __init__(self, station_id):
1910+
self.station_id = station_id
1911+
1912+
def change_station(self, station_id):
1913+
self.station_id = station_id
1914+
1915+
def __eq__(self, other):
1916+
return self.station_id == other.station_id
1917+
1918+
def __hash__(self):
1919+
return hash(self.station_id)
1920+
1921+
@lru_cache(maxsize=20)
1922+
def historic_rainfall(self, date, units='cm'):
1923+
'Rainfall on a given date'
1924+
# Depends on the station_id, date, and units.
1925+
18291926

18301927
Modules
18311928
=======

0 commit comments

Comments
 (0)