11import collections
22import inspect
33import warnings
4+ import json
5+ import time
46from functools import wraps
57
6- __version__ = 'v0.0.2 '
8+ __version__ = 'v0.0.3 '
79_cache = {}
810
911
10- def cached (max_items = None ):
12+ def cached (max_items = None , ttl = None ):
1113 """
1214 @cached decorator wrapper.
1315 :param max_items: The max items can be held in memoization cache
1416 * NOT RECOMMENDED *
1517 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.
1621 :return: decorator
1722 """
1823 def decorator (func ):
@@ -26,6 +31,8 @@ def decorator(func):
2631 raise TypeError ('Unable to do memoization on non-function object ' + str (func ))
2732 if max_items is not None and (not isinstance (max_items , int ) or max_items <= 0 ):
2833 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' )
2936 arg_spec = inspect .getargspec (func )
3037 if len (arg_spec .args ) == 0 and arg_spec .varargs is None and arg_spec .keywords is None :
3138 warnings .warn ('It\' s meaningless to do memoization on a function with no arguments' , SyntaxWarning )
@@ -42,15 +49,22 @@ def wrapper(*args, **kwargs):
4249 input_args = _hashable_args (args , kwargs )
4350 function_id = id (func )
4451 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
4655 cache_unit = specified_cache [input_args ]
4756 cache_unit ['access_count' ] += 1
4857 return cache_unit ['result' ]
49- else : # not yet cached
58+ else :
59+ # not yet cached
5060 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
5262 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 }
5468 return output
5569 return wrapper
5670 return decorator
@@ -118,10 +132,10 @@ def _hashable_args(args, kwargs):
118132 :param kwargs: kwargs
119133 :return: a hashable string
120134 """
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 )
125139
126140
127141def _is_function (obj ):
@@ -171,5 +185,5 @@ def _retrieve_safe_function_id(func):
171185
172186if __name__ == '__main__' :
173187 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 ' )
175189 sys .stderr .write ('Go to https://github.com/lonelyenvoy/python-memoization for usage and more details.\n ' )
0 commit comments