1- from dataclasses import dataclass
21from threading import RLock
3- from typing import Any , Hashable , Optional , Set
2+ from typing import Any , Hashable , Optional , Dict , Set
3+ from aiomisc .cache .base import CacheBase
44
55
6+ # noinspection PyShadowingBuiltins
67class Node :
7- __slots__ = ( 'prev' , 'next' , 'items' )
8+ __slots__ = 'prev' , 'next' , 'items'
89
910 def __init__ (self , prev : "Node" = None , next : "Node" = None ,
1011 items : Optional [Set ["Item" ]] = None ):
@@ -13,14 +14,16 @@ def __init__(self, prev: "Node" = None, next: "Node" = None,
1314 self .items = items or set ()
1415
1516
16- @dataclass (frozen = True )
1717class Item :
18- node : Node
19- key : Hashable
20- value : Any
18+ __slots__ = 'node' , 'key' , 'value'
2119
20+ def __init__ (self , node : Node , key : Hashable , value : Any ):
21+ self .node : Node = node
22+ self .key : Hashable = key
23+ self .value : Any = value
2224
23- class LFUCache :
25+
26+ class LFUCache (CacheBase ):
2427 """
2528 LFU cache implementation
2629
@@ -29,20 +32,37 @@ class LFUCache:
2932 >>> assert "foo" in lfu
3033 >>> lfu.get('foo')
3134 'bar'
35+
36+ >>> lfu.remove('foo')
37+ >>> assert "foo" not in lfu
38+ >>> lfu.get("foo")
39+ Traceback (most recent call last):
40+ ...
41+ KeyError: 'foo'
42+ >>> lfu.remove("foo")
43+
3244 >>> lfu.set("bar", "foo")
3345 >>> lfu.set("spam", "egg")
34-
46+ >>> lfu.set("foo", "bar")
47+ >>> lfu.get("foo")
48+ 'bar'
49+ >>> lfu.get("spam")
50+ 'egg'
51+ >>> assert len(lfu) == 3
52+ >>> lfu.set("egg", "spam")
53+ >>> assert len(lfu) == 3
3554 """
3655
37- def __init__ (self , max_size : int = 0 ):
38- self .cache = dict ()
39- self .usages : Node = Node (prev = None , next = None , items = set ())
40- self .lock = RLock ()
41- self .size = 0
42- self .max_size = max_size
56+ __slots__ = "cache" , "usages" , "lock"
57+
58+ def __init__ (self , max_size : int ):
59+ super ().__init__ (max_size )
60+ self .cache : Dict [Hashable , Item ] = dict ()
61+ self .usages : Node = Node (prev = None , next = None )
62+ self .lock : RLock = RLock ()
4363
4464 def _create_node (self ) -> Node :
45- node = Node (prev = self .usages , next = None , items = set () )
65+ node = Node (prev = self .usages , next = None )
4666 self .usages .next = node
4767 return node
4868
@@ -55,11 +75,7 @@ def _update_usage(self, item: Item):
5575 new_node = self ._create_node ()
5676
5777 old_node .items .remove (item )
58- item = Item (
59- node = new_node ,
60- key = item .key ,
61- value = item .value ,
62- )
78+ item .node = new_node
6379 new_node .items .add (item )
6480 self .cache [item .key ] = item
6581
@@ -69,11 +85,29 @@ def _update_usage(self, item: Item):
6985 self .usages = new_node
7086 self .usages .prev = None
7187
88+ def _remove_item (self , item : Item ):
89+ with self .lock :
90+ if item .key in self .cache :
91+ self .cache .pop (item .key , None )
92+
93+ if item in item .node .items :
94+ item .node .items .remove (item )
95+
96+ item .node = None
97+
7298 def get (self , key : Hashable ):
7399 item : Item = self .cache [key ]
74100 self ._update_usage (item )
75101 return item .value
76102
103+ def remove (self , key : Hashable ):
104+ with self .lock :
105+ item : Optional [Item ] = self .cache .pop (key , None )
106+ if item is None :
107+ return
108+
109+ self ._remove_item (item )
110+
77111 def set (self , key : Hashable , value : Any ):
78112 with self .lock :
79113 node : Optional [Node ] = self .usages
@@ -85,8 +119,30 @@ def set(self, key: Hashable, value: Any):
85119 node .items .add (item )
86120 self .cache [key ] = item
87121
88- def __contains__ (self , key ) -> Any :
122+ if self ._is_overflow ():
123+ self ._on_overflow ()
124+
125+ def _on_overflow (self ):
126+ with self .lock :
127+ while self ._is_overflow ():
128+ if not self .usages .items :
129+ if self .usages .next is not None :
130+ self .usages .next .prev = None
131+ self .usages = self .usages .next
132+ else :
133+ self .usages = Node (prev = None , next = None )
134+
135+ item = self .usages .items .pop ()
136+ self ._remove_item (item )
137+
138+ def _is_overflow (self ) -> bool :
139+ return len (self .cache ) > self .max_size
140+
141+ def __contains__ (self , key : Hashable ) -> Any :
89142 if key in self .cache :
90143 self ._update_usage (self .cache [key ])
91144 return True
92145 return False
146+
147+ def __len__ (self ):
148+ return len (self .cache )
0 commit comments