Implementing LRU Cache Decorator in Python

LRU is the cache replacement algorithm that removes the least recently used data and stores the new data. Suppose we have a cache space of 10 memory frames. And each frame is filled with a file. Now if we want to store the new file, we need to remove the oldest file in the cache and add the new file. This is how LRU works.

LRU cache consists of Queue and Dictionary data structures.

  • Queue: to store the most recently used to least recently used files
  • Hash table: to store the file and its position in the cache

Refer to the below articles to get more information about the topic:

What is decorator?

A decorator is a function that takes a function as its only parameter and returns a function. This is helpful to “wrap” functionality with the same code over and over again.

Note: For more information, refer to Decorators in Python.

Now, after getting the basic idea about the LRU and Decorators in Python, let’s have a look at the implementation of the LRU cache Decorator in Python.

filter_none

edit
close

play_arrow

link
brightness_4
code

from collections import deque
  
  
# LRU cache implementation
class LRUCache:
      
    def __init__(self, size=5):
        self.size = size
        self.container = deque()
        self.map = dict() 
       
       
    def reallocate(self):
        # to reallocate the hashmap for
        # every access of file access file
        # will reallocate the data in hashmap
        # according to the numbers position 
        # in the container for every access,
        # hit and miss(evict)
        if len(self.container) > 1:
              
            for key, val in enumerate(self.container):
                self.map[val] = key
       
       
    def access(self, val):
          
        # print("access "+str(val))
        self.container.remove(val)
        self.container.appendleft(val)
        self.reallocate()
       
    def evict(self, val):
          
        # print("cache miss "+str(val))
        if val in self.map:
            #del self.map[val]
            self.container.remove(val)
          
        else:
            x = self.container.pop()
            del self.map[x]
          
        self.normal_insert(val)
           
    def normal_insert(self, val):
        self.container.appendleft(val)
        self.reallocate()
           
    def insert(self, val):
          
        if val in self.map.keys():
              
            # if vlaue in present in
            # the hashmap then it is a hit.
            # access function will access the
            # number already present and replace
            # it to leftmost position
            self.access(val)
              
        else:
            # if value is not present in
            # the hashtable
            if (len(self.map.keys()) == self.size):
                # if the size of the queue
                # is equal to capacity and
                # we try to insert the number,
                # then it is a miss then,
                # evict function will delete the
                # right most elements and insert
                # the latest element in the
                # leftmost position
                self.evict(val)
                  
            else:
                # normal_insert function will normally
                # insert the data into the cache..
                self.normal_insert(val)
       
    def print(self):
        lru_elements = [x for x in self.container]
        print(lru_elements)
   
   
  
# definition of lru decorator
def LRUDecorator(size):
      
    lru = LRUCache(size)
      
    def decorator(function):
          
        def functionality(num):
            lru.insert(num)
            lru.print()
              
            # to check the num pageframe(position)
            # uncomment the below statement
            # print(lur.map)
            print(num, function(num))
        return functionality
      
    return decorator
   
      
# Using LRU Decorator
@LRUDecorator(size=4)
def ex_func_01(n):
    print(f'Computing...{n}')
    time.sleep(1)
    return n
           
print(f'\nFunction: ex_func_01')
print(ex_func_01(1))
print(ex_func_01(2))
print(ex_func_01(3))
print(ex_func_01(4))
print(ex_func_01(1))
print(ex_func_01(2))
print(ex_func_01(5))
print(ex_func_01(1))
print(ex_func_01(2))
print(ex_func_01(3))
print(ex_func_01(4))
print(ex_func_01(5))

chevron_right


Output:

Function: ex_func_01
[1]
Computing...1
1 1
None
[2, 1]
Computing...2
2 2
None
[3, 2, 1]
Computing...3
3 3
None
[4, 3, 2, 1]
Computing...4
4 4
None
[1, 4, 3, 2]
Computing...1
1 1
None
[2, 1, 4, 3]
Computing...2
2 2
None
[5, 2, 1, 4]
Computing...5
5 5
None
[1, 5, 2, 4]
Computing...1
1 1
None
[2, 1, 5, 4]
Computing...2
2 2
None
[3, 2, 1, 5]
Computing...3
3 3
None
[4, 3, 2, 1]
Computing...4
4 4
None
[5, 4, 3, 2]
Computing...5
5 5
None



My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.



Improved By : Akanksha_Rai

Article Tags :

Be the First to upvote.


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.