Function wrapper and python decorator

When researchers translate their thoughts into codes, they usually have the needs to modify their existing functions to accommodate their new ideas, such as adding new arguments or a little more computations to the functions.

Wrapper functions can be used as an interface to adapt to the existing codes, so as to save you from modifying your codes back and forth. As an example, you might be writing functions to do some calculations.

def my_add(m1, p1=0):
    output_dict = {}
    output_dict['r1'] = m1+p1
    return output_dic
def my_deduct(m1, p1=0):
    output_dict = {}
    output_dict['r1'] = m1-p1
    return output_dic

Now if you need to print out the arguments to ensure your calculations are correct, you can always do something like:

def my_add(m1, p1=0):
    print_my_arguments
    output_dict = {}
    output_dict['r1'] = m1+p1
    return output_dict

def my_deduct(m1, p1=0):
    print_my_arguments
    output_dict = {}
    output_dict['r1'] = m1-p1
    return output_dict 

But I realize that print_my_arguments can be a routine that I will use repeatedly. Besides, I want my functions focusing on the calculation only. Therefore, I can use the decorator like this:

def display_arguments(func):
    def display_and_call(*args, **kwargs):      
        args = list(args)
        print('must-have arguments are:')
        for i in args:
            print(i)          
        print('optional arguments are:')
        for kw in kwargs.keys():
            print( kw+'='+str( kwargs[kw] ) )          
        return func(*args, **kwargs)   
    return display_and_call

@display_arguments
def my_add(m1, p1=0):
    output_dict = {}
    output_dict['r1'] = m1+p1
    return output_dict

@display_arguments
def my_deduct(m1, p1=0):
    output_dict = {}
    output_dict['r1'] = m1-p1
    return output_dict

This is why decorators are also called ‘wrappers’. When python sees \@display_arguments, it calls the display_arguments with the wrapped functions (my_add or my_deduct) as the argument. And the display_arguments defines a new function called display_and_call, which is a modified version of the wrapped function. Every time we call my_add (or my_deduct), we are calling the display_and_call, which prints out the arguments before running the wrapped functions.

my_add(100,p1=1)

must-have arguments are:
100
optional arguments are:
p1=1
Out:{'r1': 101}

Here we note that all arguments all passed using (*args, **kwargs) when defining the wrapper and when calling the function.
The * and ** operators are performing the operation called packing. True to its name, what it does is to pack all the arguments into a single tuple argument called args, and to pack all the keyword arguments into a single dictionary argument called kwargs.

Also, we can wrap functions without using decorators. For example, I want to redefine my input data structure for my calculation functions, but I don’t want to touch the codes of my_add and my_deduct because they have been wrapped up in some packages. What I can do is first to write a reform_argument function to pre-process the data_strucutre, and then write a wrapper function to wrap the pre-process together with my_add function.

new_input_dict={
    'm1':100,
    'p1':1,
}

def reform_argument(input_dict):
    m1 = input_dict.get('m1')
    p1 = input_dict.get('p1')
    return m1,p1

def wrapped_my_add(new_input_dict):
    m1,p1= reform_argument(new_input_dict)
    return my_add(m1,p1=p1)

wrapped_my_add(new_input_dict)

This blog is from the feedback provided by Helio, Yang, Sebastian, and Hanyu, for the code review on March 2nd, 2017.