"""Generic decorators that are used within :mod:`mutwo`."""importcopyimportfunctoolsimportinspectimportosimporttypesimporttyping__all__=("add_copy_option","add_tag_to_class","compute_lazy")frommutwoimportcore_utilitiesF=typing.TypeVar("F",bound=typing.Callable[...,typing.Any])
[docs]defadd_copy_option(function:F)->F:"""This decorator adds a copy option for object mutating methods. :arg function: The method which shall be adjusted. The 'add_copy_option' decorator adds the 'mutate' keyword argument to the decorated method. If 'mutate' is set to ``False``, the decorator deep copies the respective object, then applies the called method on the new copied object and finally returns the copied object. This can be useful for methods that by default mutate its object. When adding this method, it is up to the user whether the original object shall be changed and returned (for mutate=True) or if a copied version of the object with the respective mutation shall be returned (for mutate=False). """@functools.wraps(function)defwrapper(self,*args,mutate:bool=True,**kwargs)->typing.Any:ifmutateisTrue:function(self,*args,**kwargs)returnselfelse:deep_copied_object=copy.deepcopy(self)function(deep_copied_object,*args,**kwargs)returndeep_copied_objectwrapped_function=typing.cast(F,wrapper)wrapped_function.__annotations__.update({"mutate":bool})returnwrapped_function
G=typing.TypeVar("G")
[docs]defadd_tag_to_class(class_to_decorate:G)->G:"""This decorator adds a 'tag' argument to the init method of a class. :arg class_to_decorate: The class which shall be decorated. """init=class_to_decorate.__init__definit_with_tag(self,*args,tag:typing.Optional[str]=None,**kwargs):init(self,*args,**kwargs)self.tag=tagclass_to_decorate.__init__=init_with_tagreturnclass_to_decorate
[docs]defcompute_lazy(path:str,force_to_compute:bool=False,pickle_module:typing.Optional[types.ModuleType]=None,):"""Cache function output to disk via pickle. :param path: Where to save the computed result. :type path: str :param force_to_compute: Set to ``True`` if function has to be re-computed. :type force_to_compute: bool :param pickle_module: Depending on the object which should be pickled the default python pickle module won't be sufficient. Therefore alternative third party pickle modules (with the same API) can be used. If no argument is provided, the function will first try to use any of the pickle modules given in the :const:`mutwo.core_utilities.configurations.PICKLE_MODULE_TO_SEARCH_TUPLE`. If none of the modules could be imported it will fall back to the buildin pickle module. :type pickle_module: typing.Optional[types.ModuleType] The decorator will only run the function if its input changes and otherwise load the return value from the disk. This function is helpful if there is a complex, long-taking calculation, which should only run once or from time to time if the input changes. **Example:** >>> from mutwo.utilities import decorators >>> @decorators.compute_lazy("magic_output", False) def my_super_complex_calculation(n_numbers): return sum(number for number in range(n_numbers)) >>> N_NUMBERS = 100000000 >>> my_super_complex_calculation(N_NUMBERS) 4999999950000000 >>> # takes very little time when calling the function the second time >>> my_super_complex_calculation(N_NUMBERS) 4999999950000000 >>> # takes long again, because the input changed >>> my_super_complex_calculation(N_NUMBERS + 10) 4999999950000000 """ifpickle_moduleisNone:for(pickle_module_name)incore_utilities.configurations.PICKLE_MODULE_TO_SEARCH_TUPLE:try:pickle_module=__import__(pickle_module_name)exceptImportError:passelse:breakifpickle_moduleisNone:pickle_module=__import__("pickle")defdecorator(function_to_decorate:F)->F:@functools.wraps(function_to_decorate)defwrapper(*args,**kwargs)->typing.Any:has_to_compute=Falsecurrent_args_and_kwargs=(args,kwargs)is_file=os.path.isfile(path)ifnotis_file:has_to_compute=Trueelse:withopen(path,"rb")asf:function_result,previous_args_and_kwargs=pickle_module.load(f)ifprevious_args_and_kwargs!=current_args_and_kwargs:has_to_compute=Trueifhas_to_computeorforce_to_compute:function_result=function_to_decorate(*args,**kwargs)withopen(path,"wb")asf:pickle_module.dump((function_result,current_args_and_kwargs),f)returnfunction_resultwrapped_function=typing.cast(F,wrapper)returnwrapped_functionreturndecorator