Package: decoripy
Decoripydecoripy provides a well-structured template class for creating Python decorators. It uses inheritance to be efficient
and simple.Table of contentsMotivationUsage1. MotivationWith decoripy, writing a decorator becomes very easy. It aims to improve the Python language expressiveness by
enhancing a very powerful Python mechanism.Decoripy provides a template built upon the basic wrapping of a function, hiding the implementation details, and
providing some useful advantages:no distinction between decorator with or without arguments has to be done;a temporal based execution is provided.Decorator argumentsWith decoripy you could create decorator with or without arguments with no pain.
In standard Python you should handle the arguments passed to the decorator, because, in this case, the wrapper
function does not take the function as a the first argument.
So you could do something like this:@MyDecoratordeffunction_to_decorate(var):passor@MyDecorator(True)deffunction_to_decorate(var):passor@MyDecorator(timeout=3000,num_retries=3)deffunction_to_decorate(var):passor@MyDecorator(True,timeout=3000,num_retries=3)deffunction_to_decorate(var):passand you have not to change your code.
The unnamed arguments (*args) passed to the decorator can be accessed by using the positional order (For example,
the first parameters could be taken in this way:first_arg = args[0], see Usage).
The named arguments (**kwargs) passed to the decorator are parsed and can be accessed by their name (For example,
timeout could be used in the implementation code in this way:self.timeout, see Usage).Temporal based executionThe decoripy template is built to provide temporal based execution:you could execute a pre-operationbeforethe decorated function is executed;you could do some operationwhilethe decorated function is executed;you could execute a post-operationafterthe decorated function is executed.In this way you can control the execution flow of the decorated function.Nested decoratorYou could nest more decorator. The order respects the writing order, so:fromdecoripyimportAbstractDecoratorclassFirst(AbstractDecorator):defdo_before(self,*args,**kwargs):print("Executing: First do_before")defdo(self,*args,**kwargs):print("Executing: First do")returnself.function(*args,**kwargs)defdo_after(self,*args,**kwargs):print("Executing: First do_after")classSecond(AbstractDecorator):defdo_before(self,*args,**kwargs):print("Executing: Second do_before")defdo(self,*args,**kwargs):print("Executing: Second do")returnself.function(*args,**kwargs)defdo_after(self,*args,**kwargs):print("Executing: Second do_after")@First(timeout=3000)@Seconddeffunction_to_decorate(var):print("Executing: function -",var)Result:Executing:Firstdo_before
Executing:FirstdoExecuting:Seconddo_before
Executing:SeconddoExecuting:function-True
Executing:Seconddo_after
Executing:Firstdo_after@First.do_before()is executed before, then@First.do(); while
executing it,@Second.do_before()is triggered, then@Second.do().
The last functions to be executed are@Second.do_after()and@First.do_after(), as you could expected.2. UsageIn order to create a new decorator, you have only to write a new class inheriting from the
abstract class AbstractDecorator, and overriding the following (optional) methods:do_before: if you want to make some operationbeforethe function execution.
You can also return the result of the operation and it is stored in theself.before_resultvariable. It can be used in the otherself.do()andself.do_after()functions.do: if you want to add some operationwhileexecuting the function.
It is mandatory doing theself.function(*args, **kwargs)call here to trigger the
decorated function execution.
You can also return the result of the operation and it is stored in theself.execution_resultvariable. It can be used in the other andself.do_after()functions.do_after: if you want to make some operationafterthe function execution.
You can also return the result of the operation and it is stored in theself.after_resultvariable.The overriding of the three functions is optional. Clearly, no overriding means no
operations done upon the decorated function.
Summarizing, you have only to handle the temporal phases you are interested on.You can access to the function handler through theself.functionvariable.
The decorator unnamed parameters can be accessed through theself.argsvariable.
The decorator named parameters can be accessed through theself.kwargsvaraible orExample 1 - No decorator arguments:fromdecoripyimportAbstractDecoratorclassDecoratorWithoutArguments(AbstractDecorator):defdo_before(self,*args,**kwargs):print("Executing: do_before")return"Executed: do_before"defdo(self,*args,**kwargs):print(self.before_result,", Executing: do")function_result=self.function(*args,**kwargs)returnfunction_result+", Executed: do"defdo_after(self,*args,**kwargs):print(self.execution_result,", Executing: do_after")return"Executed: do_after"@DecoratorWithoutArgumentsdeffunction_to_decorate(var1,var2,dict_var1,dict_var2):print("Executing: function: ",var1,var2,dict_var1,dict_var2)return"Executed: function"function_to_decorate(1,"var2",dict_var1=[1,2,3],dict_var2={"key":"value"})Example 2 - Decorator argumentsfromdecoripyimportAbstractDecoratorclassDecoratorWithParameters(AbstractDecorator):defdo_before(self,*args,**kwargs):ifself.execute_before:print("Executing: do_before")return"Executed: do_before"defdo(self,*args,**kwargs):# Non-existing params -> error!try:ifself.execute_do:print(self.before_result,", Executing: do")function_result=self.function(*args,**kwargs)returnfunction_result+", Executed: do"exceptAttributeError:self.function(*args,**kwargs)defdo_after(self,*args,**kwargs):ifself.execute_after:print(self.execution_result,", Executing: do_after")return"Executed: do_after"@DecoratorWithParameters(3,execute_before=False,execute_after=False)deffunction_to_decorate(var1,var2,dict_var1,dict_var2):print("Executing: function: ",var1,var2,dict_var1,dict_var2)return"Executed: function"function_to_decorate(1,"var2",dict_var1=[1,2,3],dict_var2={"key":"value"})