Skip to content
Snippets Groups Projects
Commit fcf10f1e authored by Piotr Maślanka's avatar Piotr Maślanka
Browse files

added `cache_memoize`

parent 1a519cfd
No related branches found
No related tags found
No related merge requests found
# v2.14.19
* `memoize` will release the per-function lock while calling target function
* added `cache_memoize`
......@@ -9,6 +9,8 @@ Decorators
.. autofunction:: satella.coding.decorators.memoize
.. autofunction:: satella.coding.decorators.cache_memoize
.. autofunction:: satella.coding.decorators.queue_get
.. autofunction:: satella.coding.decorators.copy_arguments
......
__version__ = '2.14.19_a1'
__version__ = '2.14.19_a2'
......@@ -2,7 +2,7 @@ from .arguments import auto_adapt_to_methods, attach_arguments, for_argument, \
execute_before, copy_arguments, replace_argument_if, transform_result, \
transform_arguments
from .decorators import wraps, chain_functions, has_keys, short_none, memoize, return_as_list, \
default_return
default_return, cache_memoize
from .flow_control import loop_while, queue_get
from .preconditions import postcondition, precondition
from .retry_dec import retry
......@@ -12,4 +12,4 @@ __all__ = ['retry', 'transform_result', 'transform_arguments',
'chain_functions', 'has_keys', 'short_none', 'auto_adapt_to_methods',
'attach_arguments', 'for_argument', 'loop_while', 'memoize',
'copy_arguments', 'replace_argument_if', 'return_as_list',
'default_return']
'default_return', 'cache_memoize']
import inspect
import time
import typing as tp
import warnings
......@@ -130,6 +131,44 @@ def return_as_list(ignore_nulls: bool = False):
return outer
def cache_memoize(cache_duration: float, time_getter: tp.Callable[[], float] = time.monotonic):
"""
A thread-safe memoizer that memoizes the return value for at most cache_duration seconds.
:param cache_duration: cache validity, in seconds
:param time_getter: a callable without arguments that yields us a time marker
"""
from satella.coding.concurrent import MonitorDict, Monitor
def outer(fun):
fun.memoize_timestamps = MonitorDict()
fun.memoize_values = {}
@wraps(fun)
def inner(*args, **kwargs):
now = time_getter()
with Monitor.acquire(fun.memoize_timestamps):
if args in fun.memoize_timestamps:
ts = fun.memoize_timestamps[args]
if now - ts > cache_duration:
with Monitor.release(fun.memoize_timestamps):
v = fun(*args, **kwargs)
fun.memoize_timestamps[args] = now
fun.memoize_values[args] = v
else:
with Monitor.release(fun.memoize_timestamps):
v = fun(*args, **kwargs)
fun.memoize_timestamps[args] = now
fun.memoize_values[args] = v
return fun.memoize_values[args]
return inner
return outer
def memoize(fun):
"""
A thread safe memoizer based on function's ONLY positional arguments.
......@@ -147,7 +186,8 @@ def memoize(fun):
if args in fun.memoizer:
return fun.memoizer[args]
else:
v = fun(*args, **kwargs)
with Monitor.release(fun.memoizer):
v = fun(*args, **kwargs)
fun.memoizer[args] = v
return v
......
......@@ -3,11 +3,13 @@ import queue
import unittest
from socket import socket
import time
from satella.coding import wraps, chain_functions, postcondition, \
log_exceptions, queue_get, precondition, short_none
from satella.coding.decorators import auto_adapt_to_methods, attach_arguments, \
execute_before, loop_while, memoize, copy_arguments, replace_argument_if, \
retry, return_as_list, default_return, transform_result, transform_arguments
retry, return_as_list, default_return, transform_result, transform_arguments, \
cache_memoize
from satella.coding.predicates import x
from satella.exceptions import PreconditionError
......@@ -16,6 +18,23 @@ logger = logging.getLogger(__name__)
class TestDecorators(unittest.TestCase):
def test_cached_memoizer(self):
a = {'calls': 0}
@cache_memoize(1)
def returns(b):
nonlocal a
a['calls'] += 1
return b
self.assertEqual(returns(6), 6)
self.assertEqual(a['calls'], 1)
self.assertEqual(returns(6), 6)
self.assertEqual(a['calls'], 1)
time.sleep(1.1)
self.assertEqual(returns(6), 6)
self.assertEqual(a['calls'], 2)
def test_transform_arguments(self):
@transform_arguments(a='a*a')
def square(a):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment