From 63865b2740ca0c7866276342d9e850cdbd43c2be Mon Sep 17 00:00:00 2001
From: Andreas Kloeckner <inform@tiker.net>
Date: Fri, 5 Jul 2013 22:01:16 -0400
Subject: [PATCH] Allow cache clearing in memoize_method

---
 pytools/__init__.py  | 63 +++++++++++++++++++++++++++++++++-----------
 test/test_pytools.py | 24 +++++++++++++++++
 2 files changed, 71 insertions(+), 16 deletions(-)

diff --git a/pytools/__init__.py b/pytools/__init__.py
index 6066a1e..af84b1b 100644
--- a/pytools/__init__.py
+++ b/pytools/__init__.py
@@ -24,6 +24,7 @@ THE SOFTWARE.
 
 
 import operator
+import sys
 
 from pytools.decorator import decorator
 
@@ -351,7 +352,7 @@ def single_valued(iterable, equality_pred=operator.eq):
 # }}}
 
 
-# {{{ memoization
+# {{{ memoization / attribute storage
 
 @my_decorator
 def memoize(func, *args):
@@ -370,22 +371,54 @@ def memoize(func, *args):
         result = func(*args)
         func._memoize_dic[args] = result
         return result
+
 FunctionValueCache = memoize
 
 
-@my_decorator
-def memoize_method(method, instance, *args):
-    cache_dict_name = intern("_memoize_dic_"+method.__name__)
-    try:
-        return getattr(instance, cache_dict_name)[args]
-    except AttributeError:
-        result = method(instance, *args)
-        setattr(instance, cache_dict_name, {args: result})
-        return result
-    except KeyError:
-        result = method(instance, *args)
-        getattr(instance, cache_dict_name)[args] = result
-        return result
+if sys.version_info >= (2, 5):
+    # For Python 2.5 and newer, support cache deletion by a
+    # 'method_name.clear_cache(self)' call.
+
+    def memoize_method(method):
+        cache_dict_name = intern("_memoize_dic_"+method.__name__)
+
+        def wrapper(self, *args):
+            try:
+                return getattr(self, cache_dict_name)[args]
+            except AttributeError:
+                result = method(self, *args)
+                setattr(self, cache_dict_name, {args: result})
+                return result
+            except KeyError:
+                result = method(self, *args)
+                getattr(self, cache_dict_name)[args] = result
+                return result
+
+        def clear_cache(self):
+            delattr(self, cache_dict_name)
+
+        from functools import update_wrapper
+        new_wrapper = update_wrapper(wrapper, method)
+        new_wrapper.clear_cache = clear_cache
+
+        return new_wrapper
+
+else:
+    # For sad old Python 2.4, cache deletion is not supported.
+
+    @my_decorator
+    def memoize_method(method, instance, *args):
+        cache_dict_name = intern("_memoize_dic_"+method.__name__)
+        try:
+            return getattr(instance, cache_dict_name)[args]
+        except AttributeError:
+            result = method(instance, *args)
+            setattr(instance, cache_dict_name, {args: result})
+            return result
+        except KeyError:
+            result = method(instance, *args)
+            getattr(instance, cache_dict_name)[args] = result
+            return result
 
 
 def memoize_method_nested(inner):
@@ -424,8 +457,6 @@ def memoize_method_nested(inner):
 
     return new_inner
 
-FunctionValueCache = memoize
-
 # }}}
 
 
diff --git a/test/test_pytools.py b/test/test_pytools.py
index 7507971..f8d0109 100644
--- a/test/test_pytools.py
+++ b/test/test_pytools.py
@@ -1,5 +1,8 @@
 from __future__ import division
 
+import pytest
+import sys  # noqa
+
 
 def test_memoize_method_nested():
     from pytools import memoize_method_nested
@@ -21,3 +24,24 @@ def test_memoize_method_nested():
     sc = SomeClass()
     sc.f()
     assert sc.run_count == 1
+
+
+@pytest.mark.skipif("sys.version_info < (2, 5)")
+def test_memoize_method_clear():
+    from pytools import memoize_method
+
+    class SomeClass:
+        def __init__(self):
+            self.run_count = 0
+
+        @memoize_method
+        def f(self):
+            self.run_count += 1
+            return 17
+
+    sc = SomeClass()
+    sc.f()
+    sc.f()
+    assert sc.run_count == 1
+
+    sc.f.clear_cache(sc)
-- 
GitLab