Декоратор работает с l oop, но не с циклом while - PullRequest
2 голосов
/ 27 марта 2020

Я пытаюсь научиться использовать декораторы в Python, но это сложная задача. Я сделал декоратор, который должен выполнять декорированную функцию указанное количество раз. Мне удалось создать код, который выполняет эту задачу:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper_repeat(x):
            for _ in range(num_times):
                func(x)
        return wrapper_repeat
    return decorator_repeat

@repeat(4)
def helloWorld(say):
    print(say)

helloWorld("Hey everyone!")

Затем я попытался воспроизвести этот код еще раз, но на этот раз я использовал пока l oop вместо l oop как следует:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper_repeat(x):
            while num_times > 0:
                func(x)
                num_times -= 1
        return wrapper_repeat
    return decorator_repeat

@repeat(4)
def helloWorld(say):
    print(say)

helloWorld("Hey everyone!")

но теперь функция возвращает ошибку.

Traceback (most recent call last):
  File "untitled.py", line 118, in <module>
    helloWorld("Hey everyone!")
  File "untitled.py", line 108, in wrapper_repeat
    while num_times > 0:
UnboundLocalError: local variable 'num_times' referenced before assignment

Для меня эти функции должны работать одинаково, но это не так. Можете ли вы помочь мне понять, что не так с моим кодом?

Спасибо!

1 Ответ

3 голосов
/ 27 марта 2020

Разница в том, что версия с while назначает переменную num_times. Это делает его локальным по отношению к функции wrapper_repeat() по умолчанию, так что это не то же самое, что переменная num_times из repeat() .. Вы должны объявить ее нелокальной:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper_repeat(x):
            nonlocal num_times
            while num_times > 0:
                func(x)
                num_times -= 1
        return wrapper_repeat
    return decorator_repeat

Обратите внимание, что у этого определения есть другая проблема. Поскольку вы изменяете захваченную переменную замыкания, значение будет сохраняться между вызовами декорированной функции. Поэтому, если вы вызовете helloWorld во второй раз, он вообще не повторится, потому что условие while не выполнено.

Лучшее определение копирует num_times во временную переменную. Это решает обе проблемы.

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper_repeat(x):
            num = num_times
            while num > 0:
                func(x)
                num -= 1
        return wrapper_repeat
    return decorator_repeat
...