запутанный путь выполнения кода с декораторами в python - PullRequest
0 голосов
/ 21 апреля 2020

Я вхожу в Python и просто немного борюсь с этим примером кода с сайта "Real Python".

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!")

Вот результат, полученный при нескольких вызовах функции "say_whee".

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

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

>>> say_whee.num_calls
2

Недоумение вызывает то, что строка "wrapper_count_calls.num_calls = 0" не кажется, выполняется, когда вызывается функция «say_whee», хотя она имеет декоратор «@count_calls»

Ответы [ 2 ]

2 голосов
/ 21 апреля 2020

Вы немного неправильно поняли концепцию декораторов.

count_calls будет вызываться только один раз, то есть когда вы используете его для украшения say_whee. Например, запустите этот код:

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!")

Вывод

count_calls is called

Но когда вызывается фактический say_whee, запускается только wrapper_count_calls:

say_whee()
wrapper_count_calls is called
Call 1 of 'say_whee'
Whee!
1 голос
/ 21 апреля 2020

Недоумение вызывает то, что строка "wrapper_count_calls.num_calls = 0", по-видимому, не выполняется, когда вызывается функция "say_whee", даже если она имеет декоратор "@count_calls"

Декораторы - это почти тривиальный синтаксис c sugar:

@foo
def bar():
    ...

desugars для:

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

и это все.

Так как строка, которая первоначально устанавливает атрибут, находится на верхнем уровне декоратора, который он выполняет при вызове самого декоратора, то есть сразу после определения функции.

Затем он «заменяет» функцию, которую он декорирует, внутренним wrapper_count_calls, то есть то, что вызывается (и вызывает упакованную функцию), увеличивая атрибут.

...