简体   繁体   中英

perplexing code execution path with decorators in python

I'm getting into Python and just struggling a bit with this piece of example code from the "Real Python" site.

import functools

def count_calls(func):
    def wrapper_count_calls(*args, **kwargs):
        wrapper_count_calls.num_calls += 1
        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)
    wrapper_count_calls.num_calls = 0
    return wrapper_count_calls

@count_calls
def say_whee():
    print("Whee!")

Here's the output given a couple of calls to the "say_whee" function.

>>> say_whee()
Call 1 of 'say_whee'
Whee!

>>> say_whee()
Call 2 of 'say_whee'
Whee!

>>> say_whee.num_calls
2

The bit that's perplexing is that the line "wrapper_count_calls.num_calls = 0" does not seem to be executed when the "say_whee" function is called even though it has the decorator "@count_calls"

You misunderstood the concept of decorators a little bit.

count_calls will only be called once, ie when you use it to decorate say_whee . For example, run this code:

import functools

def count_calls(func):
    def wrapper_count_calls(*args, **kwargs):
        print("wrapper_count_calls is called")
        wrapper_count_calls.num_calls += 1
        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)
    wrapper_count_calls.num_calls = 0
    print("count_calls is called")
    return wrapper_count_calls

@count_calls
def say_whee():
    print("Whee!")

Output

count_calls is called

But when the actual say_whee is called, only wrapper_count_calls runs:

say_whee()
wrapper_count_calls is called
Call 1 of 'say_whee'
Whee!

The bit that's perplexing is that the line "wrapper_count_calls.num_calls = 0" does not seem to be executed when the "say_whee" function is called even though it has the decorator "@count_calls"

Decorators are an almost trivial bit of syntactic sugar:

@foo
def bar():
    ...

desugars to:

def bar():
    ...
bar = foo(bar)

and that's about it.

Since the line which initially sets the attribute is at the toplevel of the decorator it's executed when the decorator itself is called, which is right after the function is defined.

It then "replaces" the function it decorates by the inner wrapper_count_calls , which is what gets called (and calls the wrapped function), incrementing the attribute.

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