Skip to content

Instantly share code, notes, and snippets.

@VehpuS
Last active April 21, 2024 11:08
Show Gist options
  • Save VehpuS/cdf4e0f67eb3ab2b3eb5 to your computer and use it in GitHub Desktop.
Save VehpuS/cdf4e0f67eb3ab2b3eb5 to your computer and use it in GitHub Desktop.
An implementation for a dictionary with values that are evaluated only on their first access (or when accessing their values using .values).
import inspect
import types
class lazy_dict(dict):
'''
@summary:
An object that allows to store in a key the return results of functions when they are accessed for the first
time (AKA lazy evaluation).
You may use this storage mechanism by passing functions or lambdas without parameters.
'''
def __setitem__(self, key, value):
'''
@summary:
Assign values to the key if the cannot be called, and evluate the key's value on access if the value is a
function / lambda with no arguments. For any other value (i.e. class declarations or functions with
parameters, return clear exceptions.
'''
# if we passed an uncallable value (i.e. a string or integer) - just assign it.
if not hasattr(value, "__call__"):
return dict.__setitem__(self, key, value)
# an alternative behavior: create a function that returns the value directly
# func = lambda: value
not_a_function = ("In order to pass a callable object that is not a function, please wrap it in a lambda expression."
"(i.e. lambda: obj()).")
assert isinstance(value, types.FunctionType), not_a_function
arg_err = ("This function requires additional arguments in order to be evaluated.\n"
"If you wish to evaluate it's return value on access, wrap it in a lambda call returning the \n"
"function with it's arguments (i.e. lambda: func(arg1, arg2, kwarg1=val1,...)).\n"
"If you wish to store a pointer to this function, create a lambda that returns this pointer "
"(i.e. lambda: func).")
no_args_argspec = inspect.getargspec(lambda: None)
assert inspect.getargspec(value) == no_args_argspec, arg_err
# if we passed an evaluation function, it will be used when first accessing the variable.
try:
self.__delitem__(key)
except:
pass
set_func_name = '_evaluate_' + str(key)
func = value
return setattr(self, set_func_name, func)
def __getitem__(self, key):
'''
@summary:
Try to use dict.__getitem__.
If an exception occurs, check if the value has not been evaluated, and if that's the case
evaluate it and return it. Otherwise, raise the exception from dict.__getitem__.
'''
try:
return dict.__getitem__(self, key)
except Exception, err:
try:
set_func_name = '_evaluate_' + str(key)
func = getattr(self, set_func_name)
except:
raise err
try:
result = func()
except Exception, err:
raise Exception("The evaluation function passed to %s failed with the following %s exception:\n%s" %
(key, str(type(err)), str(err)))
dict.__setitem__(self, key, result)
delattr(self, set_func_name)
return dict.__getitem__(self, key)
def __delitem__(self, key):
'''
@summary:
Deleting a key will delete it's generating function if it hasn't been evaluated yet.
'''
try:
return dict.__delitem__(self, key)
except Exception, err:
try:
set_func_name = '_evaluate_' + str(key)
return delattr(self, set_func_name)
except:
raise err
def __contains__(self, key):
'''
@summary:
A key that has not been evaluated is still in the dictionary.
'''
set_func_name = '_evaluate_' + str(key)
return dict.__contains__(self, key) or hasattr(self, set_func_name)
def __repr__(self):
"""
@summary:
Extends the behavior of dict.__repr__ to display keys without values for unevaluated keys as well
"""
class LazyValue():
def __repr__(self):
return "**not evaluated yet**"
repr_dict = dict(self)
for attr in dir(self):
if (attr[0:len('_evaluate_')] == '_evaluate_'):
key = attr[len('_evaluate_'):]
repr_dict[key] = LazyValue()
return "lazy_dict-> " + repr(repr_dict)
def keys(self):
"""
@summary:
Extends the behavior of dict.keys to display keys for unevaluated keys as well
"""
evaluated_keys = [key for key in dict(self)]
not_evaluated_keys = [attr[len('_evaluate_'):] for attr in dir(self) if attr[0:len('_evaluate_')] == '_evaluate_']
return evaluated_keys + not_evaluated_keys
def __iter__(self):
"""
@summary:
Extends the behavior of dict.__iter__ to iterate on keys for unevaluated keys as well
"""
for key in self.keys():
yield key
def values(self):
"""
@summary:
Extends the behavior of dict.values to return values for unevaluated keys as well.
These values will be calculated when this command is run (i.e. not deferred).
"""
return [self[key] for key in self]
def __len__(self):
'''
@summary:
The length of a lazy_dict is the number of keys stored inside it
'''
return len(self.keys())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment