Reputation: 63
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
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