From 4b779b3785c0014224eef95c8805f166d0ef2a86 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 15 Oct 2011 23:50:42 -0700 Subject: [PATCH] Issue 13177: Make tracebacks more readable by avoiding chained exceptions in the lru_cache. --- Lib/functools.py | 34 +++++++++++++++++++--------------- Lib/test/test_functools.py | 16 ++++++++++++++++ Misc/NEWS | 4 ++++ 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/Lib/functools.py b/Lib/functools.py index 03bcb1e69f5..85ea257abd8 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -141,7 +141,7 @@ def lru_cache(maxsize=100): hits = misses = 0 kwd_mark = (object(),) # separates positional and keyword args - lock = Lock() # needed because ordereddicts aren't threadsafe + lock = Lock() # needed because OrderedDict isn't threadsafe if maxsize is None: cache = dict() # simple cache without ordering or size limit @@ -155,13 +155,15 @@ def lru_cache(maxsize=100): try: result = cache[key] hits += 1 + return result except KeyError: - result = user_function(*args, **kwds) - cache[key] = result - misses += 1 + pass + result = user_function(*args, **kwds) + cache[key] = result + misses += 1 return result else: - cache = OrderedDict() # ordered least recent to most recent + cache = OrderedDict() # ordered least recent to most recent cache_popitem = cache.popitem cache_renew = cache.move_to_end @@ -171,18 +173,20 @@ def lru_cache(maxsize=100): key = args if kwds: key += kwd_mark + tuple(sorted(kwds.items())) - try: - with lock: + with lock: + try: result = cache[key] - cache_renew(key) # record recent use of this key + cache_renew(key) # record recent use of this key hits += 1 - except KeyError: - result = user_function(*args, **kwds) - with lock: - cache[key] = result # record recent use of this key - misses += 1 - if len(cache) > maxsize: - cache_popitem(0) # purge least recently used cache entry + return result + except KeyError: + pass + result = user_function(*args, **kwds) + with lock: + cache[key] = result # record recent use of this key + misses += 1 + if len(cache) > maxsize: + cache_popitem(0) # purge least recently used cache entry return result def cache_info(): diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 7d11b53a71c..270cab00756 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -655,6 +655,22 @@ class TestLRU(unittest.TestCase): self.assertEqual(fib.cache_info(), functools._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)) + def test_lru_with_exceptions(self): + # Verify that user_function exceptions get passed through without + # creating a hard-to-read chained exception. + # http://bugs.python.org/issue13177 + for maxsize in (None, 100): + @functools.lru_cache(maxsize) + def func(i): + return 'abc'[i] + self.assertEqual(func(0), 'a') + with self.assertRaises(IndexError) as cm: + func(15) + self.assertIsNone(cm.exception.__context__) + # Verify that the previous exception did not result in a cached entry + with self.assertRaises(IndexError): + func(15) + def test_main(verbose=None): test_classes = ( TestPartial, diff --git a/Misc/NEWS b/Misc/NEWS index 77bc91f6b8f..b10ecf418b7 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -46,6 +46,10 @@ Library - Issue #13158: Fix decoding and encoding of GNU tar specific base-256 number fields in tarfile. +- Issue #13177: Functools lru_cache() no longer calls the original function + inside an exception handler. This makes tracebacks easier to read because + chained exceptions are avoided. + - Issue #13025: mimetypes is now reading MIME types using the UTF-8 encoding, instead of the locale encoding.