Skip to content

Commit

Permalink
fixes #580
Browse files Browse the repository at this point in the history
  • Loading branch information
jph00 committed Jul 13, 2024
1 parent d2bd7e5 commit 33f4f0f
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 12 deletions.
1 change: 1 addition & 0 deletions fastcore/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@
'fastcore.xtras.shufflish': ('xtras.html#shufflish', 'fastcore/xtras.py'),
'fastcore.xtras.sparkline': ('xtras.html#sparkline', 'fastcore/xtras.py'),
'fastcore.xtras.stringfmt_names': ('xtras.html#stringfmt_names', 'fastcore/xtras.py'),
'fastcore.xtras.timed_cache': ('xtras.html#timed_cache', 'fastcore/xtras.py'),
'fastcore.xtras.trace': ('xtras.html#trace', 'fastcore/xtras.py'),
'fastcore.xtras.truncstr': ('xtras.html#truncstr', 'fastcore/xtras.py'),
'fastcore.xtras.type2str': ('xtras.html#type2str', 'fastcore/xtras.py'),
Expand Down
32 changes: 28 additions & 4 deletions fastcore/xtras.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
'ReindexCollection', 'get_source_link', 'truncstr', 'sparkline', 'modify_exception', 'round_multiple',
'set_num_threads', 'join_path_file', 'autostart', 'EventTimer', 'stringfmt_names', 'PartialFormatter',
'partial_format', 'utc2local', 'local2utc', 'trace', 'modified_env', 'ContextManagers', 'shufflish',
'console_help', 'hl_md', 'type2str', 'dataclass_src', 'nullable_dc', 'make_nullable', 'mk_dataclass']
'console_help', 'hl_md', 'type2str', 'dataclass_src', 'nullable_dc', 'make_nullable', 'mk_dataclass',
'timed_cache']

# %% ../nbs/03_xtras.ipynb 2
from .imports import *
Expand All @@ -21,6 +22,7 @@
import string,time
from contextlib import contextmanager,ExitStack
from datetime import datetime, timezone
from time import sleep,time,perf_counter

# %% ../nbs/03_xtras.ipynb 7
def walk(
Expand Down Expand Up @@ -504,7 +506,7 @@ class EventTimer:

def __init__(self, store=5, span=60):
import collections
self.hist,self.span,self.last = collections.deque(maxlen=store),span,time.perf_counter()
self.hist,self.span,self.last = collections.deque(maxlen=store),span,perf_counter()
self._reset()

def _reset(self): self.start,self.events = self.last,0
Expand All @@ -515,10 +517,10 @@ def add(self, n=1):
self.hist.append(self.freq)
self._reset()
self.events +=n
self.last = time.perf_counter()
self.last = perf_counter()

@property
def duration(self): return time.perf_counter()-self.start
def duration(self): return perf_counter()-self.start
@property
def freq(self): return self.events/self.duration

Expand Down Expand Up @@ -682,3 +684,25 @@ def mk_dataclass(cls):
if not hasattr(cls,k) or getattr(cls,k) is MISSING:
setattr(cls, k, field(default=None))
dataclass(cls, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

# %% ../nbs/03_xtras.ipynb 180
def timed_cache(seconds=60, maxsize=128):
"Like `lru_cache`, but also with time-based eviction"
def decorator(func):
cache = {}
@wraps(func)
def wrapper(*args, **kwargs):
key = str(args) + str(kwargs)
now = time()
if key in cache:
result, timestamp = cache[key]
if seconds == 0 or now - timestamp < seconds:
cache[key] = cache.pop(key)
return result
del cache[key]
result = func(*args, **kwargs)
cache[key] = (result, now)
if len(cache) > maxsize: cache.pop(next(iter(cache)))
return result
return wrapper
return decorator
89 changes: 81 additions & 8 deletions nbs/03_xtras.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"from functools import wraps\n",
"import string,time\n",
"from contextlib import contextmanager,ExitStack\n",
"from datetime import datetime, timezone"
"from datetime import datetime, timezone\n",
"from time import sleep,time,perf_counter"
]
},
{
Expand All @@ -47,7 +48,6 @@
"from nbdev.showdoc import *\n",
"from fastcore.nb_imports import *\n",
"\n",
"from time import sleep\n",
"import shutil,tempfile,pickle,random\n",
"from dataclasses import dataclass"
]
Expand Down Expand Up @@ -1727,7 +1727,7 @@
{
"data": {
"text/plain": [
"['h', 'g', 'a', 'd', 'b', 'e', 'f', 'c']"
"['a', 'h', 'f', 'b', 'c', 'g', 'e', 'd']"
]
},
"execution_count": null,
Expand Down Expand Up @@ -2114,7 +2114,7 @@
"\n",
" def __init__(self, store=5, span=60):\n",
" import collections\n",
" self.hist,self.span,self.last = collections.deque(maxlen=store),span,time.perf_counter()\n",
" self.hist,self.span,self.last = collections.deque(maxlen=store),span,perf_counter()\n",
" self._reset()\n",
"\n",
" def _reset(self): self.start,self.events = self.last,0\n",
Expand All @@ -2125,10 +2125,10 @@
" self.hist.append(self.freq)\n",
" self._reset()\n",
" self.events +=n\n",
" self.last = time.perf_counter()\n",
" self.last = perf_counter()\n",
"\n",
" @property\n",
" def duration(self): return time.perf_counter()-self.start\n",
" def duration(self): return perf_counter()-self.start\n",
" @property\n",
" def freq(self): return self.events/self.duration"
]
Expand Down Expand Up @@ -2188,8 +2188,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Num Events: 3, Freq/sec: 256.2\n",
"Most recent: ▁▃▇▅▂ 223.9 260.6 307.1 279.5 252.0\n"
"Num Events: 3, Freq/sec: 316.2\n",
"Most recent: ▇▁▂▃▁ 288.7 227.7 246.5 256.5 217.9\n"
]
}
],
Expand Down Expand Up @@ -2839,6 +2839,79 @@
"Person(name=\"Bob\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from functools import wraps\n",
"from time import time"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def timed_cache(seconds=60, maxsize=128):\n",
" \"Like `lru_cache`, but also with time-based eviction\"\n",
" def decorator(func):\n",
" cache = {}\n",
" @wraps(func)\n",
" def wrapper(*args, **kwargs):\n",
" key = str(args) + str(kwargs)\n",
" now = time()\n",
" if key in cache:\n",
" result, timestamp = cache[key]\n",
" if seconds == 0 or now - timestamp < seconds:\n",
" cache[key] = cache.pop(key)\n",
" return result\n",
" del cache[key]\n",
" result = func(*args, **kwargs)\n",
" cache[key] = (result, now)\n",
" if len(cache) > maxsize: cache.pop(next(iter(cache)))\n",
" return result\n",
" return wrapper\n",
" return decorator"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"@timed_cache(seconds=0.1, maxsize=2)\n",
"def cached_func(x): return x * 2, time()\n",
"\n",
"# basic caching\n",
"result1, time1 = cached_func(2)\n",
"test_eq(result1, 4)\n",
"sleep(0.01)\n",
"result2, time2 = cached_func(2)\n",
"test_eq(result2, 4)\n",
"test_eq(time1, time2)\n",
"\n",
"# caching different values\n",
"result3, _ = cached_func(3)\n",
"test_eq(result3, 6)\n",
"\n",
"# maxsize\n",
"_, time4 = cached_func(4)\n",
"_, time2_new = cached_func(2)\n",
"test_close(time2, time2_new, eps=0.1)\n",
"_, time3_new = cached_func(3)\n",
"test_ne(time3_new, time())\n",
"\n",
"# time expiration\n",
"sleep(0.2)\n",
"_, time4_new = cached_func(4)\n",
"test_ne(time4_new, time())"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down

0 comments on commit 33f4f0f

Please sign in to comment.