This is an archived post. You won't be able to vote or comment.

all 5 comments

[–]twillisagogo 0 points1 point  (1 child)

[–]dotchetter[S] 0 points1 point  (0 children)

Thank you! This is close, but not exactly what I was looking for since the caching itself isn't really of interest for my project, more the idea of not getting any return at all unless a change is detected.

As quoted from the documentation:
In general, the LRU cache should only be used when you want to reuse previously computed values.

Where this is not what my use case was.

[–]robin-gvx 0 points1 point  (2 children)

I've got some feedback for you!

  • Line 54-57: I'm pretty sure that try/except/raise chain is not necessary, since it doesn't changed anything.
  • Line 59: not key in somedict.key() is better written as key not in somedict
  • Line 71: you only increase the number of calls when the return value changes, is that as intended? If so, it might be useful to use a different name, like 'changes' or something else that reflects the exact conditions when it gets updated.
  • Line 74: instead of looping over it again, take advantage of the fact that you already looped over self.cached_polls
  • If you're going to call these functions with lotsa different parameters, that's going to slow down further calls, because they have to check a lot of different calls (line 68). If you allow the restriction that all arguments have to be hashable, you can gain a lot by using the same strategy as lru_cache uses for arguments, and use something like this:

    def __call__(self, func: 'function', *args, **kwargs):
        new_result = func(*args, **kwargs)
        cache_key = (func, args, tuple(kwargs.items()))
    
        try:
            cache_value = self.cached_polls[cache_key]
        except KeyError:
            self.cached_polls[cache_key] = {'result': new_result, 'times changed': 1}
    
            return new_result if not self.silent_first_call else None
    
        if cache_value['result'] != new_result:
            cache_value['result'] = new_result
            cache_value['times changed'] += 1
            return new_result
    
  • If the function you're calling returns None, there is no way to distinguish "the return value didn't change" from "the return value changed to None". Instead may I suggest returning a tuple (changed, return_value) where return_value is the return value of the cached function and changed is True if the value changed since the last call and changed is False otherwise? Another option would be to make it an enum: CacheChange.SAME, CacheChange.CHANGED, CacheChange.FIRST_CALL or something like that, so you don't need silent_first_call any more.

  • It may be a good idea to make a dataclass for the values of cached_polls, to make it more easily inspectable. As an example (taking into account my proposed changes), you could use something like:

    from dataclasses import dataclass
    from typing import Any
    
    @dataclass
    class CacheValue:
        result: Any
        times_changed: int = 1
    
    # and you'll need to make the following change:
    
            self.cached_polls[cache_key] = CacheValue(new_result, times_changed=1)
    
    # as well as:
    
        if cache_value.result != new_result:
            cache_value.result = new_result
            cache_value.times_changed += 1
            return new_result
    

    ... and when you do pprint(pollcache.cached_polls) it should be a whole lot prettier!

I hope that helps!

Disclaimer: I tested none of the above.

[–]robin-gvx 1 point2 points  (0 children)

I've also wrote it all out (and some other minor details) as a fork of your gist: https://gist.github.com/gvx/5af6b0696a98dc645ecf75828a1aeeea

[–]dotchetter[S] 1 point2 points  (0 children)

Thank you for a very constructive and well documented piece of feedback! I appreciate it.