From b609e34c0820c1eb0df213f9cf2a08e4e8907259 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 9 May 2020 16:49:45 -0700 Subject: [PATCH 1/4] bpo-40571: Make lru_cache(maxsize=None) more discoverable --- Doc/library/functools.rst | 26 +++++++++++++++++++ Lib/functools.py | 11 +++++++- .../2020-05-09-15-38-25.bpo-40571.kOXZGC.rst | 2 ++ 3 files changed, 38 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 856c1c790ae361..8f4e93cbe17193 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, typed=False)``, 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 * fact(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 f05b106b62c007..8721d08f9e8ddc 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 @@ def cache_clear(): pass +################################################################################ +### cache -- simplified access to the infinity cache +################################################################################ + +def cache(user_function, /): + 'Simple lightweight unbounded cache. Sometimes called "memoize".' + return lru_cache(maxsize=None, typed=False)(user_function) + + ################################################################################ ### singledispatch() - single-dispatch generic function decorator ################################################################################ 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 00000000000000..476770f6974d2f --- /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). From bb42c0566b7c793a99fb80ad4a8c4303ea53b3b7 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 9 May 2020 18:16:49 -0700 Subject: [PATCH 2/4] Add test. Drop unnecessary default argument. --- Lib/functools.py | 2 +- Lib/test/test_functools.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Lib/functools.py b/Lib/functools.py index 8721d08f9e8ddc..87c7d87438998b 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -894,7 +894,7 @@ def cache_clear(): def cache(user_function, /): 'Simple lightweight unbounded cache. Sometimes called "memoize".' - return lru_cache(maxsize=None, typed=False)(user_function) + return lru_cache(maxsize=None)(user_function) ################################################################################ diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index b3893a15566fa6..e122fe0b333402 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1432,6 +1432,25 @@ def check_order_with_hash_seed(seed): 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): From 8fd8217625a10ae2d48e2def96f5fb52ca1b37c2 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 9 May 2020 18:18:02 -0700 Subject: [PATCH 3/4] Simplify the equivalent code --- Doc/library/functools.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 8f4e93cbe17193..93dc546111cea3 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -31,7 +31,7 @@ The :mod:`functools` module defines the following functions: Simple lightweight unbounded function cache. Sometimes called `"memoize" `_. - Returns the same as ``lru_cache(maxsize=None, typed=False)``, creating a thin + 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. From a412632534c7ddf5a535db12c19c5429b9ab0a32 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 10 May 2020 09:15:41 -0700 Subject: [PATCH 4/4] Fix typo --- Doc/library/functools.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 93dc546111cea3..204e66ae5ac407 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -40,7 +40,7 @@ The :mod:`functools` module defines the following functions: @cache def factorial(n): - return n * fact(n-1) if n else 1 + return n * factorial(n-1) if n else 1 >>> factorial(10) # no previously cached result, makes 11 recursive calls 3628800