简体   繁体   中英

Python 3 - Decorators execution flow

The below example is taken from python cookbook 3rd edition section 9.5. I placed break points at each line to understand the flow of execution . Below is the code sample, its output and the questions I have . I have tried to explain my question , let me know if you need further info.

from functools import wraps, partial
import logging

# Utility decorator to attach a function as an attribute of obj
def attach_wrapper(obj, func=None):
    if func is None:
        return partial(attach_wrapper, obj)
    setattr(obj, func.__name__, func)
    return func

def logged(level, name=None, message=None):


    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, logmsg)
            return func(*args, **kwargs)


        @attach_wrapper(wrapper)
        def set_message(newmsg):
            nonlocal logmsg
            logmsg = newmsg


        return wrapper
    return decorate

# Example use
@logged(logging.DEBUG)
def add(x, y):
    return x + y


logging.basicConfig(level=logging.DEBUG)
add.set_message('Add called')
#add.set_level(logging.WARNING)
print (add(2, 3))

output is

DEBUG:__main__:Add called
5

I understand the concept of decorators, but this is confusing a little.

scenario 1 . When the following line is debugged @logged(logging.DEBUG) , we get decorate = .decorate at 0x000000000< memoryaddress >>

Question : why would the control go back to execute the function " def decorate" ? Is it because the "decorate" function is on the top of the stack ?

scenario 2 :When executing @attach_wrapper(wrapper) , the control goes to execute attach_wrapper(obj, func=None) and partial function returns func =

question : why would the control go back to execute def attach_wrapper(obj, func=None): and how would this time the value for func is *.decorate..set_message at 0x000000000 > being passed to the attach_wrapper ?

Scenario 1

This:

@logged(logging.DEBUG)
def add(x, y):
    ....

is the same as this:

def add(x, y):
    ....
add = logged(logging.DEBUG)(add)

Note that there are two calls there: first logged(logging.DEBUG) returns decorate and then decorate(add) is called.

Scenario 2

Same as in Scenario 1 , this:

@attach_wrapper(wrapper)
def set_message(newmsg):
    ...

is the same as this:

def set_message(newmsg):
    ...
set_message = attach_wrapper(wrapper)(set_message)

Again, there are two calls: first attach_wrapper(wrapper) returns the partial object and then partial(set_message) is called.


In other words...

logged and attach_wrapper are not decorators. Those are functions which return decorators. That is why two calls are made: one to the function which returns the decorator and another the the decorator itself.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM