def decoratorFunc(*parameters):
    print('    decoratorFunc() called')
    def decoratorFuncWrapper(func):
        print('    decoratorFuncWrapper() called')
        def decoratedFunc(*args):
            print('    decoratedFunc() called')
            for (arg, parameter) in zip(args, parameters):
                print("{}: {}".format(parameter, arg))
            return func(*args)
        return decoratedFunc
    return decoratorFuncWrapper

# Change the "1" to a "0" to do it the same thing without a decorator.
if 0: # with a decorator
    @decoratorFunc('a', 'b')
    def arbitraryFunction(a, b):
        print("arbitraryFunction called")
else: # without a decorator (semantically equivalent, with the steps annotated)
    print('step 1: call the decorator, which returns the wrapper function')
    wrapperFunc = decoratorFunc('a', 'b')
    print('step 2: define the function to be wrapped')
    def arbitraryFunction(a, b):
        print("arbitraryFunction called")
    print('step 3: wrap the function to be wrapped')
    arbitraryFunction = wrapperFunc(arbitraryFunction)
    del wrapperFunc # remove it from the namespace (optional, for equivalence)

print('step 4: in body of module, call the wrapped function')
arbitraryFunction(3, 7)
