Python in-memory cache with time to live

后端 未结 8 1512
后悔当初
后悔当初 2020-12-04 23:24

I have multiple threads running the same process that need to be able to to notify each other that something should not be worked on for the next n seconds its not the end o

相关标签:
8条回答
  • 2020-12-05 00:02

    In case you don't want to use any 3rd libraries, you can add one more parameter to your expensive function: ttl_hash=None. This new parameter is so-called "time sensitive hash", its the only purpose is to affect lru_cache.

    For example:

    from functools import lru_cache
    import time
    
    
    @lru_cache()
    def my_expensive_function(a, b, ttl_hash=None):
        del ttl_hash  # to emphasize we don't use it and to shut pylint up
        return a + b  # horrible CPU load...
    
    
    def get_ttl_hash(seconds=3600):
        """Return the same value withing `seconds` time period"""
        return round(time.time() / seconds)
    
    
    # somewhere in your code...
    res = my_expensive_function(2, 2, ttl_hash=get_ttl_hash())
    # cache will be updated once in an hour
    
    
    0 讨论(0)
  • 2020-12-05 00:02

    You can also go for dictttl, which has MutableMapping, OrderedDict and defaultDict(list)

    Initialize an ordinary dict with each key having a ttl of 30 seconds

    data = {'a': 1, 'b': 2}
    dict_ttl = DictTTL(30, data)
    

    OrderedDict

    data = {'a': 1, 'b': 2}
    dict_ttl = OrderedDictTTL(30, data)
    

    defaultDict(list)

    dict_ttl = DefaultDictTTL(30)
    data = {'a': [10, 20], 'b': [1, 2]}
    [dict_ttl.append_values(k, v) for k, v in data.items()]
    
    0 讨论(0)
  • 2020-12-05 00:06

    You can use the expiringdict module:

    The core of the library is ExpiringDict class which is an ordered dictionary with auto-expiring values for caching purposes.

    In the description they do not talk about multithreading, so in order not to mess up, use a Lock.

    0 讨论(0)
  • 2020-12-05 00:09

    Something like that ?

    from time import time, sleep
    import itertools
    from threading import Thread, RLock
    import signal
    
    
    class CacheEntry():
      def __init__(self, string, ttl=20):
        self.string = string
        self.expires_at = time() + ttl
        self._expired = False
    
      def expired(self):
        if self._expired is False:
          return (self.expires_at < time())
        else:
          return self._expired
    
    class CacheList():
      def __init__(self):
        self.entries = []
        self.lock = RLock()
    
      def add_entry(self, string, ttl=20):
        with self.lock:
            self.entries.append(CacheEntry(string, ttl))
    
      def read_entries(self):
        with self.lock:
            self.entries = list(itertools.dropwhile(lambda x:x.expired(), self.entries))
            return self.entries
    
    def read_entries(name, slp, cachelist):
      while True:
        print "{}: {}".format(name, ",".join(map(lambda x:x.string, cachelist.read_entries())))
        sleep(slp)
    
    def add_entries(name, ttl, cachelist):
      s = 'A'
      while True:
        cachelist.add_entry(s, ttl)
        print("Added ({}): {}".format(name, s))
        sleep(1)
        s += 'A'
    
    
    
    if __name__ == "__main__":
      signal.signal(signal.SIGINT, signal.SIG_DFL)
    
      cl = CacheList()
      print_threads = []
      print_threads.append(Thread(None, read_entries, args=('t1', 1, cl)))
      # print_threads.append(Thread(None, read_entries, args=('t2', 2, cl)))
      # print_threads.append(Thread(None, read_entries, args=('t3', 3, cl)))
    
      adder_thread = Thread(None, add_entries, args=('a1', 2, cl))
      adder_thread.start()
    
      for t in print_threads:
        t.start()
    
      for t in print_threads:
        t.join()
    
      adder_thread.join()
    
    0 讨论(0)
  • 2020-12-05 00:13

    I absolutely love the idea from @iutinvg, I just wanted to take it a little further. Decouple it from having to know to pass the ttl and just make it a decorator so you don't have to think about it. If you have django, py3 and don't feel like pip installing any dependencies, try this out.

    import time
    from django.utils.functional import lazy
    from functools import lru_cache, partial, update_wrapper
    
    
    def lru_cache_time(seconds, maxsize=None):
        """
        Adds time aware caching to lru_cache
        """
        def wrapper(func):
            # Lazy function that makes sure the lru_cache() invalidate after X secs
            ttl_hash = lazy(lambda: round(time.time() / seconds), int)()
            
            @lru_cache(maxsize)
            def time_aware(__ttl, *args, **kwargs):
                """
                Main wrapper, note that the first argument ttl is not passed down. 
                This is because no function should bother to know this that 
                this is here.
                """
                def wrapping(*args, **kwargs):
                    return func(*args, **kwargs)
                return wrapping(*args, **kwargs)
            return update_wrapper(partial(time_aware, ttl_hash), func)
        return wrapper
    
    
    @lru_cache_time(seconds=10)
    def meaning_of_life():
        """
        This message should show up if you call help().
        """
        print('this better only show up once!')
        return 42
    
    
    @lru_cache_time(seconds=10)
    def multiply(a, b):
        """
        This message should show up if you call help().
        """
        print('this better only show up once!')
        return a * b
        
    # This is a test, prints a `.` for every second, there should be 10s 
    # between each "this better only show up once!" *2 because of the two functions.
    for _ in range(20):
        meaning_of_life()
        multiply(50, 99991)
        print('.')
        time.sleep(1)
    
    0 讨论(0)
  • 2020-12-05 00:17

    Regarding an expiring in-memory cache, for general purpose use, a common design pattern to typically do this is not via a dictionary, but via a function or method decorator. A cache dictionary is managed behind the scenes. As such, this answer somewhat complements the answer by User which uses a dictionary rather than a decorator.

    The ttl_cache decorator in cachetools==3.1.0 works a lot like functools.lru_cache, but with a time to live.

    import cachetools.func
    
    @cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
    def example_function(key):
        return get_expensively_computed_value(key)
    
    
    class ExampleClass:
        EXP = 2
    
        @classmethod
        @cachetools.func.ttl_cache()
        def example_classmethod(cls, i):
            return i * cls.EXP
    
        @staticmethod
        @cachetools.func.ttl_cache()
        def example_staticmethod(i):
            return i * 3
    
    0 讨论(0)
提交回复
热议问题