Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] expose the cache, key and lock arguments of @cachetools.cached as attributes on the decorated function #176

Closed
Cnoor0171 opened this issue Jul 9, 2020 · 2 comments

Comments

@Cnoor0171
Copy link

Cnoor0171 commented Jul 9, 2020

This feature is more just for convenience, so I understand if its far down in the list of desirable features. But I think it would allow users to write cleaner code when they need to interact with the underlying cache object if we expose cache, key and lock arguments of @cachetools.cached as attributes on the decorated function.

The documentation currently recommends storing the cache in a variable if you need to access it directly. Simplified example from docs:

cache = LRUCache(maxsize=32)

@cached(cache)
def get_pep(num):
    url = 'http://www.python.org/dev/peps/pep-%04d/' % num
    with urllib.request.urlopen(url) as s:
        return s.read()

if need_invalidating:
   cache.clear()

While that's really easy to do, it forms an implicit relationship between the function and and the cache object which isn't ideal in the long run. If you have multiple functions that are cached, you'll probably prepend the cache variable's name with the function name like so:

get_pep_cache = LRUCache(maxsize=32)
fib_cache = LRUCache(maxsize=32)

@cached(cache)
def get_pep(num):
    url = 'http://www.python.org/dev/peps/pep-%04d/' % num
    with urllib.request.urlopen(url) as s:
        return s.read()

@cached(cache)
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

if pep_need_invalidating:
   get_pep_cache .clear()

But wait! To properly access the cache, you also need access to the key and lock arguments that the cache was created with! So,

get_pep_cache = LRUCache(maxsize=32)
get_pep_key = ...
get_pep_lock = ...
fib_cache = LRUCache(maxsize=32)
fib_key = ...
fib_lock = ...

@cached(get_pep_cache , get_pep_key , get_pep_lock )
def get_pep(num):
    url = 'http://www.python.org/dev/peps/pep-%04d/' % num
    with urllib.request.urlopen(url) as s:
        return s.read()

@cached(fib_cache , fib_key , fib_lock )
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

If instead, we exposed these arguments as attributes (like we already do with __wrapped__) the code becomes much simpler, cleaner and the implicit "prepended" relations become explicit attribute accesses. Like so,

@cached(LRUCache(maxsize=32), ..., ...)
def get_pep(num):
    url = 'http://www.python.org/dev/peps/pep-%04d/' % num
    with urllib.request.urlopen(url) as s:
        return s.read()

@cached(LRUCache(maxsize=32), ..., ...)
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

with fib.lock:
   del fib.cache[fib.key(2)]

This also has the added advantage that users don't need worry about whether the default key was used during cache creation (like in #173) and they can always rely on fib.cache[fib.key(2)] working.

To summarize the advantages:

  • Explicit rather implicit tie between function and its cache
  • Consistency in how we interact with the cache
  • Lowers namespace pollution
  • Less lines of code
  • The user doesn't need to think about whether they want to access the cache before writing the cache
@tkem
Copy link
Owner

tkem commented Jul 10, 2020

IMHO, if you're trying to do something more complex so you really need access to these, you should properly not use the decorators at all, but better use the cache classes directly. The decorators are there for the most simple use cases, and the fact that you can do tricky stuff with them doesn't necessarily mean you always should. So the awkwardness of manipulating caches outside of the decorator is somewhat intended.

@tkem tkem added this to the v5.0.0 milestone Oct 20, 2021
@tkem
Copy link
Owner

tkem commented Oct 20, 2021

Maybe I'll change my mind about this, due to the apparent popularity of the decorators...
Additionally to cache, cache_key and cache_lock attributes, there should probably also a cache_remove or cache_del function as suggested in #218, as well as the (somewhat) expected cache_info and cache_clear functions requested in #131.
If you want full performance, you can always use the cache classes directly...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants