Instead of trying to open calls (which you have found to not work), you can force your decorator to maintain a reference to the handle returned by shelve.open , and then if it exists and remains open, reuse for subsequent calls:
import shelve import functools def _check_cache(cache_, key, func, args, kwargs): if key in cache_: print("Using cached results") return cache_[key] else: print("No cached results, calling function") result = func(*args, **kwargs) cache_[key] = result return result def cache(filename): def decorating_function(user_function): def wrapper(*args, **kwds): args_key = str(hash(functools._make_key(args, kwds, typed=False))) func_key = '.'.join([user_function.__module__, user_function.__name__]) key = func_key + args_key handle_name = "{}_handle".format(filename) if (hasattr(cache, handle_name) and not hasattr(getattr(cache, handle_name).dict, "closed") ): print("Using open handle") return _check_cache(getattr(cache, handle_name), key, user_function, args, kwds) else: print("Opening handle") with shelve.open(filename, writeback=True) as c: setattr(cache, handle_name, c)
Output:
Opening handle No cached results, calling function outside function Using open handle No cached results, calling function inside function Again Opening handle Using cached results
Edit:
You can also implement the decorator using WeakValueDictionary , which looks a bit more readable:
from weakref import WeakValueDictionary _handle_dict = WeakValueDictionary() def cache(filename): def decorating_function(user_function): def wrapper(*args, **kwds): args_key = str(hash(functools._make_key(args, kwds, typed=False))) func_key = '.'.join([user_function.__module__, user_function.__name__]) key = func_key + args_key handle_name = "{}_handle".format(filename) if handle_name in _handle_dict: print("Using open handle") return _check_cache(_handle_dict[handle_name], key, user_function, args, kwds) else: print("Opening handle") with shelve.open(filename, writeback=True) as c: _handle_dict[handle_name] = c return _check_cache(c, key, user_function, args, kwds) return functools.update_wrapper(wrapper, user_function) return decorating_function
As soon as there are no other descriptor references, it will be removed from the dictionary. Since our descriptor goes beyond the scope when the external call to the decorated function ends, we will always have a record in the dict while the handle is open, and the record immediately after it is closed.