From 21cdb711e3b1975398c54141e519ead02670610e Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 11 May 2020 17:00:53 -0700 Subject: [PATCH] bpo-40571: Make lru_cache(maxsize=None) more discoverable (GH-20019) --- Doc/library/functools.rst | 26 +++++++++++++++++++ Lib/functools.py | 11 +++++++- Lib/test/test_functools.py | 19 ++++++++++++++ .../2020-05-09-15-38-25.bpo-40571.kOXZGC.rst | 2 ++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2020-05-09-15-38-25.bpo-40571.kOXZGC.rst diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 856c1c790ae..204e66ae5ac 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -26,6 +26,32 @@ function for the purposes of this module. The :mod:`functools` module defines the following functions: +.. decorator:: cache(user_function) + + Simple lightweight unbounded function cache. Sometimes called + `"memoize" `_. + + Returns the same as ``lru_cache(maxsize=None)``, creating a thin + wrapper around a dictionary lookup for the function arguments. Because it + never needs to evict old values, this is smaller and faster than + :func:`lru_cache()` with a size limit. + + For example:: + + @cache + def factorial(n): + return n * factorial(n-1) if n else 1 + + >>> factorial(10) # no previously cached result, makes 11 recursive calls + 3628800 + >>> factorial(5) # just looks up cached value result + 120 + >>> factorial(12) # makes two new recursive calls, the other 10 are cached + 479001600 + + .. versionadded:: 3.9 + + .. decorator:: cached_property(func) Transform a method of a class into a property whose value is computed once diff --git a/Lib/functools.py b/Lib/functools.py index f05b106b62c..87c7d874389 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -10,7 +10,7 @@ # See C source code for _functools credits/copyright __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', - 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', + 'total_ordering', 'cache', 'cmp_to_key', 'lru_cache', 'reduce', 'TopologicalSorter', 'CycleError', 'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod', 'cached_property'] @@ -888,6 +888,15 @@ except ImportError: pass +################################################################################ +### cache -- simplified access to the infinity cache +################################################################################ + +def cache(user_function, /): + 'Simple lightweight unbounded cache. Sometimes called "memoize".' + return lru_cache(maxsize=None)(user_function) + + ################################################################################ ### singledispatch() - single-dispatch generic function decorator ################################################################################ diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index b3893a15566..e122fe0b333 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1432,6 +1432,25 @@ class TestTopologicalSort(unittest.TestCase): self.assertEqual(run1, run2) +class TestCache: + # This tests that the pass-through is working as designed. + # The underlying functionality is tested in TestLRU. + + def test_cache(self): + @self.module.cache + def fib(n): + if n < 2: + return n + return fib(n-1) + fib(n-2) + self.assertEqual([fib(n) for n in range(16)], + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]) + self.assertEqual(fib.cache_info(), + self.module._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)) + fib.cache_clear() + self.assertEqual(fib.cache_info(), + self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)) + + class TestLRU: def test_lru(self): diff --git a/Misc/NEWS.d/next/Library/2020-05-09-15-38-25.bpo-40571.kOXZGC.rst b/Misc/NEWS.d/next/Library/2020-05-09-15-38-25.bpo-40571.kOXZGC.rst new file mode 100644 index 00000000000..476770f6974 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-05-09-15-38-25.bpo-40571.kOXZGC.rst @@ -0,0 +1,2 @@ +Added functools.cache() as a simpler, more discoverable way to access the +unbounded cache variant of lru_cache(maxsize=None).