1
1
import collections
2
2
import inspect
3
3
import warnings
4
+ import json
5
+ import time
4
6
from functools import wraps
5
7
6
- __version__ = 'v0.0.2 '
8
+ __version__ = 'v0.0.3 '
7
9
_cache = {}
8
10
9
11
10
- def cached (max_items = None ):
12
+ def cached (max_items = None , ttl = None ):
11
13
"""
12
14
@cached decorator wrapper.
13
15
:param max_items: The max items can be held in memoization cache
14
16
* NOT RECOMMENDED *
15
17
This argument, if given, can dramatically slow down the performance.
18
+ :param ttl: Time-To-Live
19
+ Defining how long the cached data is valid (in seconds)
20
+ If not given, the data in cache is valid forever.
16
21
:return: decorator
17
22
"""
18
23
def decorator (func ):
@@ -26,6 +31,8 @@ def decorator(func):
26
31
raise TypeError ('Unable to do memoization on non-function object ' + str (func ))
27
32
if max_items is not None and (not isinstance (max_items , int ) or max_items <= 0 ):
28
33
raise ValueError ('Illegal max_items <' + str (max_items ) + '>: must be a positive integer' )
34
+ if ttl is not None and ((not isinstance (ttl , int ) and not isinstance (ttl , float )) or ttl <= 0 ):
35
+ raise ValueError ('Illegal ttl <' + str (ttl ) + '>: must be a positive number' )
29
36
arg_spec = inspect .getargspec (func )
30
37
if len (arg_spec .args ) == 0 and arg_spec .varargs is None and arg_spec .keywords is None :
31
38
warnings .warn ('It\' s meaningless to do memoization on a function with no arguments' , SyntaxWarning )
@@ -42,15 +49,22 @@ def wrapper(*args, **kwargs):
42
49
input_args = _hashable_args (args , kwargs )
43
50
function_id = id (func )
44
51
specified_cache = _cache [function_id ]
45
- if input_args in specified_cache .keys (): # already cached
52
+ if input_args in specified_cache .keys () \
53
+ and (ttl is None or time .time () < specified_cache [input_args ]['expires_at' ]):
54
+ # already validly cached
46
55
cache_unit = specified_cache [input_args ]
47
56
cache_unit ['access_count' ] += 1
48
57
return cache_unit ['result' ]
49
- else : # not yet cached
58
+ else :
59
+ # not yet cached
50
60
output = func (* args , ** kwargs ) # execute func
51
- if max_items is not None and _size_explicit (function_id ) >= max_items :
61
+ if max_items is not None and _size_explicit (function_id ) >= max_items : # pop item when fully occupied
52
62
specified_cache .popitem (last = False )
53
- specified_cache [input_args ] = {'result' : output , 'access_count' : 0 } # make cache
63
+ # make cache
64
+ if ttl is not None :
65
+ specified_cache [input_args ] = {'result' : output , 'access_count' : 0 , 'expires_at' : time .time () + ttl }
66
+ else :
67
+ specified_cache [input_args ] = {'result' : output , 'access_count' : 0 }
54
68
return output
55
69
return wrapper
56
70
return decorator
@@ -118,10 +132,10 @@ def _hashable_args(args, kwargs):
118
132
:param kwargs: kwargs
119
133
:return: a hashable string
120
134
"""
121
- kwargs_str = ''
122
- for key , value in kwargs :
123
- kwargs_str += key + '=' + value + ';'
124
- return str (args ) + kwargs_str
135
+ if kwargs == {}:
136
+ return str ( args )
137
+ else :
138
+ return str (args ) + json . dumps ( kwargs )
125
139
126
140
127
141
def _is_function (obj ):
@@ -171,5 +185,5 @@ def _retrieve_safe_function_id(func):
171
185
172
186
if __name__ == '__main__' :
173
187
import sys
174
- sys .stderr .write ('python-memoization ' + __version__ + ': A minimalist functional memoization lib for Python\n ' )
188
+ sys .stderr .write ('python-memoization ' + __version__ + ': A minimalist functional caching lib for Python\n ' )
175
189
sys .stderr .write ('Go to https://github.com/lonelyenvoy/python-memoization for usage and more details.\n ' )
0 commit comments