Question

I am confused by the calling sequence of the following code piece.

#/usr/bin/python


def decorator_no_args(fn):
    if not callable(fn):
        raise TypeError

    def wrapper():
        return fn()

    print "decorator_no_args"
    return wrapper


@decorator_no_args
def foo_no_args():
    print "foo_no_args"


def decorator_args(* args, **kargs):
    def wrapper0(fn):
        def wrapper1(*args, **kargs):
            return fn(*args, **kargs)
        return wrapper1

    print "decorator_args"
    return wrapper0


@decorator_args(1)
def foo_args(arg0):
    print "foo_args"


if __name__ == "__main__":
    foo_no_args()

The output is:

decorator_no_args
decorator_args
foo_no_args

Function decorator_args() isn't called as you can see above. But why is it recorded?

Was it helpful?

Solution

2 things: decorators are applied when the function definition is executed, and the @expression syntax takes an expression; it is evaluated when decorating.

The line @decorator_args(1) calls the decorator_args() function, passing in 1 as an argument. The result of that call is used as the decorator.

Decorators are really just syntactic sugar, this:

@decorator_args(1)
def foo_args(arg0):
    print "foo_args"

is really executed as:

def foo_args(arg0):
    print "foo_args"
foo_args = decorator_args(1)(foo_args)

Note that decorator_args(1) is called. And it's return value is called again, to produce a decorated result.

When you look at decorator_args() you see it returns a decorator:

def decorator_args(* args, **kargs):
    def wrapper0(fn):
        def wrapper1(*args, **kargs):
            return fn(*args, **kargs)
        return wrapper1

    print "decorator_args"
    return wrapper0

wrapper0 is the real decorator; it is returned when decorator_args() is called. foo_args() is passed to wrapper0() (as the fn argument), and the original function is replaced by wrapper1.

It doesn't matter at all that foo_args is never called; decorators are not applied each time when you call the function, they are only applied once. If you called foo_no_args() more than once, the decorator_no_args message will not be repeated.

OTHER TIPS

If I recall, decorators are essentially functions that take a function as an argument and return another function (possibly the same). That's funception.

The two first lines of your output (the decorator_* ones) come from the functions foo_no_args and foo_args being defined, not called.

To better understand, have a look at this snippet:

def my_decorator(func):
    def wrap():
        print "Function ran"
        return func()
    print "Function defined"
    return wrap

@my_decorator
def my_function():
    print "my_function"

my_function()
my_function()
my_function()

Outputs:

Function defined
function ran
my_function
function ran
my_function
function ran
my_function
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top