Skip to content

Commit db62398

Browse files
committed
implement ttl; improve performance of memoization._hashable_args
1 parent 103e6c5 commit db62398

File tree

2 files changed

+25
-11
lines changed

2 files changed

+25
-11
lines changed

example.py renamed to examples.py

File renamed without changes.

memoization.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import collections
22
import inspect
33
import warnings
4+
import json
5+
import time
46
from 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

127141
def _is_function(obj):
@@ -171,5 +185,5 @@ def _retrieve_safe_function_id(func):
171185

172186
if __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

Comments
 (0)