1
1
from collections import OrderedDict
2
+ from collections import namedtuple
2
3
from functools import wraps
3
4
import time
4
5
@@ -61,7 +62,7 @@ def timed_get(self, key):
61
62
now = time .time ()
62
63
then , val = self .store [key ]
63
64
if (now - then ) > self .limit :
64
- del self .store [ key ]
65
+ self .__delitem__ ( key )
65
66
raise KeyError
66
67
return val
67
68
@@ -73,7 +74,7 @@ def timed_set(self, key, value, return_dont_set=False):
73
74
self .store [key ] = stamped_val
74
75
75
76
def timed_del (self , key ):
76
- del self [key ]
77
+ del self . store [key ]
77
78
78
79
79
80
class TimedCache (Timed ):
@@ -89,21 +90,36 @@ def __init__(self, seconds=None):
89
90
raise CacheError ("TimedCache expects a single argument: `seconds`" )
90
91
self .store = {}
91
92
self .limit = seconds
93
+ self .size = 0
92
94
93
95
def __getitem__ (self , key ):
94
96
return self .timed_get (key )
95
97
96
98
def __setitem__ (self , key , value ):
99
+ if key not in self .store :
100
+ self .size += 1
97
101
return self .timed_set (key , value )
98
102
99
- def __delitem (self , key ):
100
- del self .store [key ]
103
+ def __delitem__ (self , key ):
104
+ self .size -= 1
105
+ return self .timed_del (key )
101
106
102
107
def __repr__ (self ):
103
- return '<TimedCache[{}s]: {}>' .format (self .limit , [(k , self .store [k ]) for k in self .store ])
108
+ return '<TimedCache[{}s]>' .format (self .limit )
109
+
110
+ def _safe_get (self , k ):
111
+ try :
112
+ return self [k ]
113
+ except KeyError :
114
+ return None
104
115
105
116
def items (self ):
106
- return self .store .items ()
117
+ keys = list (self .store .keys ())
118
+ return (pair for pair in ((k , self ._safe_get (k )) for k in keys )
119
+ if pair [- 1 ] is not None )
120
+
121
+ def clean (self ):
122
+ [_ for _ in self .items ()]
107
123
108
124
109
125
class SizedCache (Sized ):
@@ -132,11 +148,11 @@ def __delitem__(self, key):
132
148
return self .sized_del (key )
133
149
134
150
def __repr__ (self ):
135
- return '<SizedCache[{}cap : {}size]: {} >' \
136
- .format (self .capacity , self .size , [( k , self . store [ k ]) for k in self . store ] )
151
+ return '<SizedCache[{}cap : {}size]>' \
152
+ .format (self .capacity , self .size )
137
153
138
154
def items (self ):
139
- return self .store . items ( )
155
+ return (( k , self [ k ]) for k in self .store )
140
156
141
157
142
158
class TimedSizedCache (Timed , Sized ):
@@ -157,7 +173,8 @@ def __init__(self, capacity=None, seconds=None):
157
173
158
174
def __getitem__ (self , key ):
159
175
_ = self .timed_get (key )
160
- return self .sized_get (key )
176
+ t , value = self .sized_get (key )
177
+ return value
161
178
162
179
def __setitem__ (self , key , value ):
163
180
stamped_val = self .timed_set (key , value , return_dont_set = True )
@@ -167,23 +184,54 @@ def __delitem__(self, key):
167
184
return self .sized_del (key )
168
185
169
186
def __repr__ (self ):
170
- return '<TimedSizedCache[{}cap, {}size, {}s]: {}>' \
171
- .format (self .capacity , self .size , self .limit , [(k , self .store [k ]) for k in self .store ])
187
+ return '<TimedSizedCache[{}cap, {}size, {}s]>' \
188
+ .format (self .capacity , self .size , self .limit )
189
+
190
+ def _safe_get (self , k ):
191
+ try :
192
+ return self [k ]
193
+ except KeyError :
194
+ return None
172
195
173
196
def items (self ):
174
- return self .store .items ()
197
+ keys = list (self .store .keys ())
198
+ return (pair for pair in ((k , self ._safe_get (k )) for k in keys )
199
+ if pair [- 1 ] is not None )
200
+
201
+ def clean (self ):
202
+ [_ for _ in self .items ()]
203
+
204
+
205
+ class CountDict (dict ):
206
+ def __init__ (self ):
207
+ self .size = 0
208
+ super (CountDict , self ).__init__ ()
209
+
210
+ def __setitem__ (self , key , value ):
211
+ if key not in self :
212
+ self .size += 1
213
+ super (CountDict , self ).__setitem__ (key , value )
214
+
215
+ def __delitem__ (self , key ):
216
+ self .size -= 1
217
+ super (CountDict , self ).__delitem__ (key , value )
175
218
176
219
177
220
def cached (capacity = None , seconds = None , debug = False ):
178
221
""" Decorator to wrap a function with an optionally sized or timed cache. """
222
+ info = {'hits' : 0 , 'misses' : 0 }
179
223
if capacity is not None and seconds is not None :
180
224
cache = TimedSizedCache (capacity = capacity , seconds = seconds )
225
+ info ['capacity' ] = capacity
226
+ info ['seconds' ] = seconds
181
227
elif capacity is not None :
182
228
cache = SizedCache (capacity = capacity )
229
+ info ['capacity' ] = capacity
183
230
elif seconds is not None :
184
231
cache = TimedCache (seconds = seconds )
232
+ info ['seconds' ] = seconds
185
233
else :
186
- cache = {}
234
+ cache = CountDict ()
187
235
188
236
def _cached (func ):
189
237
@wraps (func )
@@ -192,16 +240,26 @@ def wrapper(*args, **kwargs):
192
240
try :
193
241
v = cache [key ]
194
242
was_cached = True
243
+ info ['hits' ] += 1
195
244
except TypeError :
196
245
raise CacheError ('Inputs args: {}, kwargs: {} are not cacheable. All arguments must be hashable' \
197
246
.format (args , kwargs ))
198
247
except KeyError :
199
248
v = func (* args , ** kwargs )
200
249
cache [key ] = v
201
250
was_cached = False
251
+ info ['misses' ] += 1
202
252
if debug :
203
253
return v , was_cached
204
254
return v
255
+
256
+ def _cache_info ():
257
+ if hasattr (cache , 'clean' ):
258
+ cache .clean ()
259
+ info .update ({'size' : cache .size })
260
+ return info
261
+ wrapper .cache_info = _cache_info
262
+ wrapper .cache_access = cache
205
263
return wrapper
206
264
return _cached
207
265
0 commit comments