Creating separate loggers

As a logging optimization, we might want to use a specific logger for each wrapped function and not overuse the root logger for this kind of debugging output. We'll return to the logger in Chapter 16, The Logging and Warning Modules.

The following is a version of our decorator that creates a separate logger for each individual function:

def debug2(function: F) -> F:
log = logging.getLogger(function.__name__)

@functools.wraps(function)
def logged_function(*args, **kw):
log.debug("call(%r, %r)", args, kw)
result = function(*args, **kw)
log.debug("result = %r", result)
return result

return cast(F, logged_function)

This version modifies the output to look like the following:

DEBUG:ackermann:call((2, 4), {}) 
DEBUG:ackermann:call((2, 3), {}) 
DEBUG:ackermann:call((2, 2), {}) 
. 
. 
. 
DEBUG:ackermann:call( (0, 10), {} ) 
DEBUG:ackermann:result = 11 
DEBUG:ackermann:result = 11 
DEBUG:ackermann:result = 11 

The function name is now the logger name. This can be used to fine-tune the debugging output. We can now enable logging for individual functions rather than enabling debugging for all functions. 

Note that we can't trivially change the decorator and expect the decorated function to also change. After making a change to a decorator, we need to apply the revised decorator to a function. This means that debugging and experimenting with decorators can't be done trivially from the >>> interactive prompt.

Decorator development often involves creating and rerunning a script to define the decorator and apply it to example functions. In some cases, this script will also include tests or a demonstration to show that everything works as expected.

Now, let's see how to parameterize a decorator.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset