@@ -79,27 +79,185 @@ def cache_info():
79
79
return CacheInfo (hits , misses , cache .__len__ (), max_size , algorithm ,
80
80
ttl , thread_safe , order_independent , custom_key_maker is not None )
81
81
82
- def get_caching_list ():
82
+ def cache_is_empty ():
83
+ """Return True if the cache contains no elements"""
84
+ return cache .__len__ () == 0
85
+
86
+ def cache_is_full ():
87
+ """Return True if the cache is full"""
88
+ return cache .__len__ () >= max_size
89
+
90
+ def cache_contains_argument (function_arguments , alive_only = False ):
83
91
"""
84
- Get a list containing all (key, value) in the cache in an order determined by the algorithm - LFU
92
+ Return True if the cache contains a cached item with the specified function call arguments
93
+
94
+ :param function_arguments: Can be a list, a tuple or a dict.
95
+ - Full arguments: use a list to represent both positional arguments and keyword
96
+ arguments. The list contains two elements, a tuple (positional arguments) and
97
+ a dict (keyword arguments). For example,
98
+ f(1, 2, 3, a=4, b=5, c=6)
99
+ can be represented by:
100
+ [(1, 2, 3), {'a': 4, 'b': 5, 'c': 6}]
101
+ - Positional arguments only: when the arguments does not include keyword arguments,
102
+ a tuple can be used to represent positional arguments. For example,
103
+ f(1, 2, 3)
104
+ can be represented by:
105
+ (1, 2, 3)
106
+ - Keyword arguments only: when the arguments does not include positional arguments,
107
+ a dict can be used to represent keyword arguments. For example,
108
+ f(a=4, b=5, c=6)
109
+ can be represented by:
110
+ {'a': 4, 'b': 5, 'c': 6}
111
+
112
+ :param alive_only: Whether to check alive cache item only (default to False).
113
+
114
+ :return: True if the desired cached item is present, False otherwise.
85
115
"""
86
- result = []
87
- freq_node = lfu_freq_list_root .prev
88
- while freq_node != lfu_freq_list_root :
89
- cache_node = freq_node .cache_head .next
90
- while cache_node != freq_node .cache_head :
91
- result .append ((cache_node .key , cache_node .value ))
92
- cache_node = cache_node .next
93
- freq_node = freq_node .prev
94
- return result
116
+ if isinstance (function_arguments , tuple ):
117
+ positional_argument_tuple = function_arguments
118
+ keyword_argument_dict = {}
119
+ elif isinstance (function_arguments , dict ):
120
+ positional_argument_tuple = ()
121
+ keyword_argument_dict = function_arguments
122
+ elif isinstance (function_arguments , list ) and len (function_arguments ) == 2 :
123
+ positional_argument_tuple , keyword_argument_dict = function_arguments
124
+ if not isinstance (positional_argument_tuple , tuple ) or not isinstance (keyword_argument_dict , dict ):
125
+ raise TypeError ('Expected function_arguments to be a list containing a positional argument tuple '
126
+ 'and a keyword argument dict' )
127
+ else :
128
+ raise TypeError ('Expected function_arguments to be a tuple, a dict, or a list with 2 elements' )
129
+ key = make_key (positional_argument_tuple , keyword_argument_dict )
130
+ with lock :
131
+ cache_node = cache .get (key , sentinel )
132
+ if cache_node is not sentinel :
133
+ return values_toolkit .is_cache_value_valid (cache_node .value ) if alive_only else True
134
+ return False
135
+
136
+ def cache_contains_key (key , alive_only = False ):
137
+ """
138
+ Return True if the cache contains a cache item with the specified key. This function is only recommended to use
139
+ when you provide a custom key maker; otherwise, use cache_contains_argument() instead.
140
+
141
+ :param key: A key built by the default key maker or a custom key maker.
142
+
143
+ :param alive_only: Whether to check alive cache item only (default to False).
144
+
145
+ :return: True if the desired cached item is present, False otherwise.
146
+ """
147
+ with lock :
148
+ cache_node = cache .get (key , sentinel )
149
+ if cache_node is not sentinel :
150
+ return values_toolkit .is_cache_value_valid (cache_node .value ) if alive_only else True
151
+ return False
152
+
153
+ def cache_contains_result (return_value , alive_only = False ):
154
+ """
155
+ Return True if the cache contains a cache item with the specified user function return value. O(n) time
156
+ complexity.
157
+
158
+ :param return_value: A return value coming from the user function.
159
+
160
+ :param alive_only: Whether to check alive cache item only (default to False).
161
+
162
+ :return: True if the desired cached item is present, False otherwise.
163
+ """
164
+ with lock :
165
+ freq_node = lfu_freq_list_root .prev
166
+ while freq_node != lfu_freq_list_root :
167
+ cache_head = freq_node .cache_head
168
+ cache_node = cache_head .next
169
+ while cache_node != cache_head :
170
+ is_alive = values_toolkit .is_cache_value_valid (cache_node .value )
171
+ cache_result = values_toolkit .retrieve_result_from_cache_value (cache_node .value )
172
+ if cache_result == return_value :
173
+ return is_alive if alive_only else True
174
+ cache_node = cache_node .next
175
+ freq_node = freq_node .prev
176
+ return False
177
+
178
+ def cache_for_each (consumer ):
179
+ """
180
+ Perform the given action for each cache element in an order determined by the algorithm (FIFO) until all
181
+ elements have been processed or the action throws an error
182
+
183
+ :param consumer: an action function to process the cache elements. Must have 3 arguments:
184
+ def consumer(cache_key, cache_result, is_alive): ...
185
+ cache_key is built by the default key maker or a custom key maker.
186
+ cache_result is a return value coming from the user function.
187
+ is_alive is a boolean value indicating whether the cache is still alive
188
+ (if a TTL is given).
189
+ """
190
+ with lock :
191
+ freq_node = lfu_freq_list_root .prev
192
+ while freq_node != lfu_freq_list_root :
193
+ cache_head = freq_node .cache_head
194
+ cache_node = cache_head .next
195
+ while cache_node != cache_head :
196
+ is_alive = values_toolkit .is_cache_value_valid (cache_node .value )
197
+ cache_result = values_toolkit .retrieve_result_from_cache_value (cache_node .value )
198
+ consumer (cache_node .key , cache_result , is_alive )
199
+ cache_node = cache_node .next
200
+ freq_node = freq_node .prev
201
+
202
+ def cache_remove_if (predicate ):
203
+ """
204
+ Remove all cache elements that satisfy the given predicate
205
+
206
+ :param predicate: a predicate function to judge whether the cache elements should be removed. Must
207
+ have 3 arguments:
208
+ def consumer(cache_key, cache_result, is_alive): ...
209
+ cache_key is built by the default key maker or a custom key maker.
210
+ cache_result is a return value coming from the user function.
211
+ is_alive is a boolean value indicating whether the cache is still alive
212
+ (if a TTL is given).
213
+
214
+ :return: True if at least one element is removed, False otherwise.
215
+ """
216
+ removed = False
217
+ with lock :
218
+ freq_node = lfu_freq_list_root .prev
219
+ while freq_node != lfu_freq_list_root :
220
+ cache_head = freq_node .cache_head
221
+ cache_node = cache_head .next
222
+ removed_under_this_freq_node = False
223
+ while cache_node != cache_head :
224
+ is_alive = values_toolkit .is_cache_value_valid (cache_node .value )
225
+ cache_result = values_toolkit .retrieve_result_from_cache_value (cache_node .value )
226
+ if predicate (cache_node .key , cache_result , is_alive ):
227
+ removed = removed_under_this_freq_node = True
228
+ next_cache_node = cache_node .next
229
+ del cache [cache_node .key ] # delete from cache
230
+ cache_node .destroy () # modify references, drop this cache node
231
+ cache_node = next_cache_node
232
+ else :
233
+ cache_node = cache_node .next
234
+ # check whether only one cache node is left
235
+ if removed_under_this_freq_node and freq_node .cache_head .next == freq_node .cache_head :
236
+ # Getting here means that we just deleted the only data node in the cache list
237
+ # Note: there is still an empty sentinel node
238
+ # We then need to destroy the sentinel node and its parent frequency node too
239
+ prev_freq_node = freq_node .prev
240
+ freq_node .cache_head .destroy ()
241
+ freq_node .destroy ()
242
+ freq_node = prev_freq_node
243
+ else :
244
+ freq_node = freq_node .prev
245
+ return removed
95
246
96
247
# expose operations to wrapper
97
248
wrapper .cache_clear = cache_clear
98
249
wrapper .cache_info = cache_info
250
+ wrapper .cache_is_empty = cache_is_empty
251
+ wrapper .cache_is_full = cache_is_full
252
+ wrapper .cache_contains_argument = cache_contains_argument
253
+ wrapper .cache_contains_key = cache_contains_key
254
+ wrapper .cache_contains_result = cache_contains_result
255
+ wrapper .cache_for_each = cache_for_each
256
+ wrapper .cache_remove_if = cache_remove_if
257
+ wrapper .cache_make_key = make_key
99
258
wrapper ._cache = cache
100
259
wrapper ._lfu_root = lfu_freq_list_root
101
260
wrapper ._root_name = '_lfu_root'
102
- wrapper ._get_caching_list = get_caching_list
103
261
104
262
return wrapper
105
263
0 commit comments