Przemyslaw Wilk
Przemyslaw Wilk

Reputation: 63

Decorator working with for loop but not with while loop

I'm trying to learn how to use decorators in Python, but it is a challenging task. I have made a decorator that is suppose to execute decorated function specified number of times. I have managed to produce code that does this task:

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

Then, I have tried to reproduce this code again, but this time I have used while loop instead of for loop as follows:

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

but now the function returns an error.

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

To me those functions should work identically, but it is not the case. Can you help me understand what is wrong with my code?

Thank you!

Upvotes: 2

Views: 901

Answers (1)

Barmar
Barmar

Reputation: 782148

The difference is that the version with while assigns the num_times variable. That makes it local to the wrapper_repeat() function by default, so it's not the same as the num_times variable from repeat().. You need to declare it non-local:

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

Note that this definition has another problem. Since you're modifying a captured closure variable, the value will persist between calls to the decorated function. So if you call helloWorld a second time, it won't repeat at all because the while condition is not met.

A better definition copies num_times to a temporary variable. This solves both problems.

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

Upvotes: 3

Related Questions