Skip to content

Instantly share code, notes, and snippets.

@DrDougPhD
Created October 30, 2022 17:04
Show Gist options
  • Save DrDougPhD/c1212257862553532791b9b7cfd5cbd6 to your computer and use it in GitHub Desktop.
Save DrDougPhD/c1212257862553532791b9b7cfd5cbd6 to your computer and use it in GitHub Desktop.
Caching Decorator for Python
import os
import pathlib
import pickle
import logging
import numbers
logger = logging.getLogger(__name__)
enabled = True
def cache(function):
cache_location = pathlib.Path('./cache')
"""Return cached results when this project is in development instead of executing the expensive operation."""
def wrapper(*args, **kwargs):
if not enabled:
return function(*args, **kwargs)
# Produce the cache if it doesn't exist and then return it
function_module_path = f'{function.__module__}.{function.__name__}'
stringified_arguments = ''
if len(args) > 0:
stringified_arguments += ''.join(
'*['
",".join(stringify(positional_arg) for positional_arg in args),
']',
)
if len(kwargs) > 0:
if len(stringified_arguments) > 0:
stringified_arguments += ', '
stringified_arguments += ', '.join(
f'{key}={stringify(value)}'
for key, value in kwargs.items()
)
function_call_repr = f'{function_module_path}({stringified_arguments})'
function_cache_path = cache_location / f'{path_safe(function_call_repr)}.pkl'
if not function_cache_path.exists():
cache_location.mkdir(exist_ok=True, parents=True)
logger.info(f'Initializing development cache for `{function_call_repr}`')
results = function(*args, **kwargs)
with function_cache_path.open('wb') as pkl:
pickle.dump(results, pkl)
return results
# Reaching this code means this project is in development, and thus the cached result of the function should be returned.
logger.info(f'Loading development cache for {function_call_repr}')
with function_cache_path.open('rb') as pkl:
return pickle.load(pkl)
return wrapper
def stringify(argument) -> str:
if isinstance(argument, str):
return argument
if isinstance(argument, os.PathLike):
return str(argument)
if isinstance(argument, numbers.Number):
return str(argument)
# TODO: this might be a weak conversion
if '__name__' in dir(argument):
return argument.__name__
raise ValueError(f'Stringification of type `{type(argument)}` is not defined. Value = {argument}.')
def path_safe(value: str) -> str:
return value.replace(os.sep, '_')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment