Home > Software design >  Logging via decorators in python - function called twice?
Logging via decorators in python - function called twice?

Time:09-29

I am trying to log expectations via decorators and came across a code snippet (see below)

from functools import wraps

def logged(log='trace'):
    def wrap(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            logger = logging.getLogger(log)
            logger.debug("Calling function '{}' with args={} kwargs={}"
                             .format(function.__name__, args, kwargs))
            try:
                response = function(*args, **kwargs)
            except Exception as error:
                logger.debug("Function '{}' raised {} with error '{}'"
                                 .format(function.__name__,
                                         error.__class__.__name__,
                                         str(error)))
                raise error
            logger.debug("Function '{}' returned {}"
                             .format(function.__name__,
                                     response))
            return response
        return wrapper
    return wrap 


@logged()
def add(a, b):
    return a   b

The code works fine but say if I call add(2,3) it appears the add function is called twice, once inside the decorator (added value stored in response variable) and then when I actually call add(2,3) somewhere in my code.

Is my understanding correct ? If so I would ideally want the decorator to only do the following:

  • print "Calling function" with args
  • If exception occurs when I call add(2,3) log the error. This should happen without doing response = function(*args, **kwargs) in the decorator

Is that possible ?

CodePudding user response:

it appears the add function is called twice [...] Is my understanding correct?

No. You need to understand that a decorator is nothing but syntactic sugar for a function that (typically) takes a callable object (i.e. function) as an argument and returns a callable/function.

In this case there are a few layers of nesting, but ultimately, what you have after the interpreter gets to the end of the definition of the decorated function, is the inner function wrapper that masquerades as the add function.

That wrapper is what is called, when you later on call add(2, 3). And by its definition it will in turn call the "actual" add, passed to the definition of wrapper as the argument function of wrap.

Before that, the "actual" add is not called because the wrapper is not called. add is merely passed as a callable to the inner decorator and used to construct the wrapper function. The wrapper function is then returned to you after decoration.

This should happen without doing response = function(*args, **kwargs) in the decorator

If you don't call function somewhere in there, the actual function (in this case add) will never be called.

  • Related