Декораторы: почему сохраняется значение локальной переменной

Разбираюсь с декораторами в Python. Пример:

def count(f):
    total=0
        def decorated(*args, **kwargs):
            nonlocal total
            total+=1
            return f(*args, **kwargs), total
    return decorated

@count
def hello(name):
    return f"Привет, {name}!"


print(hello("Пользователь_1"))
print(hello("Пользователь_2"))

выводит:

('Привет, Пользователь_1!', 1)
('Привет, Пользователь_2!', 2)

Почему сохраняется значение у переменной total? Если заново запустить весь код, то total опять с 0 начинает отсчет.


Ответы (1 шт):

Автор решения: CrazyElf

Не стесняйтесь добавлять в код, который не очень понимаете, отладочную печать:

def count(f):
  print('count!') # <-- проверяем запуск функции count
  total=0
  def decorated(*args, **kwargs):
    print('decorated!') # <-- проверяем запуск функции decorated
    nonlocal total
    total+=1
    return f(*args, **kwargs), total
  return decorated

Вывод:

count!
decorated!
('Привет, Пользователь_1!', 1)
decorated!
('Привет, Пользователь_2!', 2)

Ну вроде бы всё понятно:

  • функция count была вызвана, когда интерпретатору встретился декоратор @count
  • в этой функции была определена и инициализирована переменная total = 0
  • функция count вернула ссылку на функцию decorated
  • вызовы функции hello теперь вызывают функцию decorated
  • при этом переменная total внутри функции decorated - это как бы переменная count.total, при каждом вызове hello используется эта переменная

Обновил ответ.
По сути тут нужно говорить о замыкании. В decorated передалась ссылка на переменную total, инициализированную в функции count. При этом, если вы задекорируете тем же декоратором другую функцию, то ещё раз вызовется функция count и снова инициализирует уже новую переменную total. И эта вторая задекорированная функция будет вести другой счётчик. Из-за чего концепция декораторов такая удобная и получается.

print(hello("Пользователь_1"))
print(hello("Пользователь_2"))
print(hello2("Пользователь_2_1"))
print(hello("Пользователь_3"))
print(hello("Пользователь_4"))
print(hello2("Пользователь_2_2"))

Вывод:

count!
count!
decorated!
('Привет, Пользователь_1!', 1)
decorated!
('Привет, Пользователь_2!', 2)
decorated!
('Привет, Пользователь_2_1!', 1)
decorated!
('Привет, Пользователь_3!', 3)
decorated!
('Привет, Пользователь_4!', 4)
decorated!
('Привет, Пользователь_2_2!', 2)
→ Ссылка