I'm having a very weird problem in a Python 3 decorator.
If I do this:
def rounds(nr_of_rounds):
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
return nr_of_rounds
return inner
return wrapper
it works just fine. However, if I do this:
def rounds(nr_of_rounds):
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
lst = []
while nr_of_rounds > 0:
lst.append(func(*args, **kwargs))
nr_of_rounds -= 1
return max(lst)
return inner
return wrapper
I get:
while nr_of_rounds > 0:
UnboundLocalError: local variable 'nr_of_rounds' referenced before assignment
In other words, I can use nr_of_rounds
in the inner function if I use it in a return, but I can't do anything else with it. Why is that?
What is Variable Scope in Python? In programming languages, variables need to be defined before using them. These variables can only be accessed in the area where they are defined, this is called scope. You can think of this as a block where you can access variables.
A decorator is a function that takes in a function and returns an augmented copy of that function. When writing closures and decorators, you must keep the scope of each function in mind. In Python, functions define scope. Closures have access to the scope of the function that returns them; the decorator's scope.
Decorators are a very powerful and useful tool in Python since it allows programmers to modify the behaviour of a function or class. Decorators allow us to wrap another function in order to extend the behaviour of the wrapped function, without permanently modifying it.
A variable is only available from inside the region it is created. This is called scope.
Since nr_of_rounds
is picked up by the closure, you can think of it as a "read-only" variable. If you want to write to it (e.g. to decrement it), you need to tell python explicitly -- In this case, the python3.x nonlocal
keyword would work.
As a brief explanation, what Cpython does when it encounters a function definition is it looks at the code and decides if all the variables are local or non-local. Local variables (by default) are anything that appear on the left-hand side of an assignment statement, loop variables and the input arguments. Every other name is non-local. This allows some neat optimizations1. To use a non-local variable the same way you would a local, you need to tell python explicitly either via a global
or nonlocal
statement. When python encounters something that it thinks should be a local, but really isn't, you get an UnboundLocalError
.
1The Cpython bytecode generator turns the local names into indices in an array so that local name lookup (the LOAD_FAST bytecode instruction) is as fast as indexing an array plus the normal bytecode overhead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With