How do I change global variable in nested scope using python? - python

My example below. I have tried adding global to the variable that I want to change but doesn't help. The error I'm getting is in the comment.
def getNumOfSwapsToSort(array):
sorted = array.copy()
sorted.sort()
swaps = 0
def swap():
currentSwaps = 0
for i, el in enumerate(array):
if ((len(array) - 1) == i) and currentSwaps != 0:
swap()
elif el != sorted[i]:
idx = sorted.index(el)
temp = array[idx]
array[idx] = el
array[i] = temp
# UnboundLocalError: local variable 'swaps' referenced before assignment
# if I use global swaps, then NameError: name 'swaps' is not defined
swaps += 1
currentSwaps += 1
swap()
return swaps

The swaps variable in your code is not global, it's nonlocal. It was defined in the enclosing function getNumOfSwapsToSort(), not in the global namespace.
Your other function, swap(), has access to this nonlocal variable, but the variable is protected from being edited from the inner function. If you try to edit or overwrite it, it's going to be treated as a new, local variable - hence the UnboundLocalError you're getting.
Try using the nonlocal statement:
nonlocal swaps
swaps += 1
Here's an article with a few simple examples which explain the concept of closure in Python.

swaps isn't a global, it's in getNumOfSwapsToSort, which is the parent scope of swap. So use a nonlocal declaration.
def swap():
nonlocal swaps
...

Related

Python: UnboundLocalError: cannot access local variable 'hitResult' where it is not associated with a value [duplicate]

This question already has answers here:
Is it possible to modify a variable in python that is in an outer (enclosing), but not global, scope?
(9 answers)
Closed 5 months ago.
I have code like this (simplified):
def outer():
ctr = 0
def inner():
ctr += 1
inner()
But ctr causes an error:
Traceback (most recent call last):
File "foo.py", line 9, in <module>
outer()
File "foo.py", line 7, in outer
inner()
File "foo.py", line 5, in inner
ctr += 1
UnboundLocalError: local variable 'ctr' referenced before assignment
How can I fix this? I thought nested scopes would have allowed me to do this. I've tried with 'global', but it still doesn't work.
If you're using Python 3, you can use the nonlocal statement to enable rebinding of a nonlocal name:
def outer():
ctr = 0
def inner():
nonlocal ctr
ctr += 1
inner()
If you're using Python 2, which doesn't have nonlocal, you need to perform your incrementing without barename rebinding (by keeping the counter as an item or attribute of some barename, not as a barename itself). For example:
...
ctr = [0]
def inner():
ctr[0] += 1
...
and of course use ctr[0] wherever you're using bare ctr now elsewhere.
The Explanation
Whenever a value is assigned to a variable inside a function, python considers that variable a local variable of that function. (It doesn't even matter if the assignment is executed or not - as long as an assignment exists in a function, the variable being assigned to will be considered a local variable of that function.) Since the statement ctr += 1 includes an assignment to ctr, python thinks that ctr is local to the inner function. Consequently, it never even tries to look at the value of the ctr variable that's been defined in outer. What python sees is essentially this:
def inner():
ctr = ctr + 1
And I think we can all agree that this code would cause an error, since ctr is being accessed before it has been defined.
(See also the docs or this question for more details about how python decides the scope of a variable.)
The Solution (in python 3)
Python 3 has introduced the nonlocal statement, which works much like the global statement, but lets us access variables of the surrounding function (rather than global variables). Simply add nonlocal ctr at the top of the innerfunction and the problem will go away:
def outer():
ctr = 0
def inner():
nonlocal ctr
ctr += 1
inner()
The Workaround (in python 2)
Since the nonlocal statement doesn't exist in python 2, we have to be crafty. There are two easy workarounds:
Removing all assignments to ctr
Since python only considers ctr a local variable because there's an assignment to that variable, the problem will go away if we remove all assignments to the name ctr. But how can we change the value of the variable without assigning to it? Easy: We wrap the variable in a mutable object, like a list. Then we can modify that list without ever assigning a value to the name ctr:
def outer():
ctr = [0]
def inner():
ctr[0] += 1
inner()
Passing ctr as an argument to inner
def outer():
ctr = 0
def inner(ctr):
ctr += 1
return ctr
ctr = inner(ctr)
How about declaring ctr outside of outer (i.e. in the global scope), or any other class/function? This will make the variable accessible and writable.

Python method arguments as None doing something funky, please explain [duplicate]

This question already has answers here:
Is it possible to modify a variable in python that is in an outer (enclosing), but not global, scope?
(9 answers)
Closed 6 months ago.
I have code like this (simplified):
def outer():
ctr = 0
def inner():
ctr += 1
inner()
But ctr causes an error:
Traceback (most recent call last):
File "foo.py", line 9, in <module>
outer()
File "foo.py", line 7, in outer
inner()
File "foo.py", line 5, in inner
ctr += 1
UnboundLocalError: local variable 'ctr' referenced before assignment
How can I fix this? I thought nested scopes would have allowed me to do this. I've tried with 'global', but it still doesn't work.
If you're using Python 3, you can use the nonlocal statement to enable rebinding of a nonlocal name:
def outer():
ctr = 0
def inner():
nonlocal ctr
ctr += 1
inner()
If you're using Python 2, which doesn't have nonlocal, you need to perform your incrementing without barename rebinding (by keeping the counter as an item or attribute of some barename, not as a barename itself). For example:
...
ctr = [0]
def inner():
ctr[0] += 1
...
and of course use ctr[0] wherever you're using bare ctr now elsewhere.
The Explanation
Whenever a value is assigned to a variable inside a function, python considers that variable a local variable of that function. (It doesn't even matter if the assignment is executed or not - as long as an assignment exists in a function, the variable being assigned to will be considered a local variable of that function.) Since the statement ctr += 1 includes an assignment to ctr, python thinks that ctr is local to the inner function. Consequently, it never even tries to look at the value of the ctr variable that's been defined in outer. What python sees is essentially this:
def inner():
ctr = ctr + 1
And I think we can all agree that this code would cause an error, since ctr is being accessed before it has been defined.
(See also the docs or this question for more details about how python decides the scope of a variable.)
The Solution (in python 3)
Python 3 has introduced the nonlocal statement, which works much like the global statement, but lets us access variables of the surrounding function (rather than global variables). Simply add nonlocal ctr at the top of the innerfunction and the problem will go away:
def outer():
ctr = 0
def inner():
nonlocal ctr
ctr += 1
inner()
The Workaround (in python 2)
Since the nonlocal statement doesn't exist in python 2, we have to be crafty. There are two easy workarounds:
Removing all assignments to ctr
Since python only considers ctr a local variable because there's an assignment to that variable, the problem will go away if we remove all assignments to the name ctr. But how can we change the value of the variable without assigning to it? Easy: We wrap the variable in a mutable object, like a list. Then we can modify that list without ever assigning a value to the name ctr:
def outer():
ctr = [0]
def inner():
ctr[0] += 1
inner()
Passing ctr as an argument to inner
def outer():
ctr = 0
def inner(ctr):
ctr += 1
return ctr
ctr = inner(ctr)
How about declaring ctr outside of outer (i.e. in the global scope), or any other class/function? This will make the variable accessible and writable.

Why am I getting a local variable referenced before assignment error? [duplicate]

This question already has answers here:
UnboundLocalError trying to use a variable (supposed to be global) that is (re)assigned (even after first use)
(14 answers)
Closed 6 years ago.
The answer should be 2 because first the main() function is called, then the first() function is called, overriding the global variable num = 0 which was defined outside of any functions, therefore rendering it a variable with global scope. Yet I get the following error:
UnboundLocalError: local variable 'num' referenced before assignment
Why am I getting this error?
def first():
num = num + 1
def main():
num = 1
first()
print(num)
num = 0
num_result = main()
print(num_result)
The OP wrote (emphasis mine):
The answer should be 2 because first the main() function is called, then the first() function is called, overriding the global variable.
It is not overriding the global variable. Unless you explicitly specify a variable as global, if there is any assignment to the variable in a function, it is assumed to be local. See also the python tutorial on defining functions where it states (emphasis mine):
More precisely, all variable assignments in a function store the value in the local symbol table; whereas variable references first look in the local symbol table, then in the local symbol tables of enclosing functions, then in the global symbol table, and finally in the table of built-in names. Thus, global variables cannot be directly assigned a value within a function (unless named in a global statement), although they may be referenced.
and the documentation of the global statement (if anyone using Python 3 is looking at this, please also take a look at the (compared to global) very useful nonlocal statement and its PEP 3104).
To "fix" your code:
def first():
global num
num = num + 1
def main():
global num
num = 1
first()
print(num)
num = 0
num_result = main()
print(num_result)
Do not use global variables in this way please. #LutzHorn has shown in his answer how to do it right.
The reason to avoid global variables is that their effect is hard to test for, and as soon as code gets complex enough, hard to reason about. If every function modifies global variables instead of properly taking parameters and returning values, understanding what the code actually does is hard as soon as one has more than a week or so of distance from it.
Inside first, num is not known. Use arguments and return statements.
def first(num):
return num + 1
def main():
num = 1
num = first(num)
return num
num = 0
num_result = main()
print(num_result)
This is a scope error. In first, there is no num before you try to add one to it. You can call a variable in a bloc if it is declared in this bloc, in an upper bloc or if it is declared global.
To pass variables, you can use arguments of the function:
def first(num):
return num + 1
def main():
shmilblik = first(1)
print(shmilblik)
"num" is not defined in the function "first". It is not a global variable, it is out of the scope of "first". To make it global just edit your function like this:
def first():
global num
num = num + 1
Using globals is bad practice in my opinion. you could achieve the same by using parameters and a return like this:
def first(num):
return num + 1
def main(num):
num = 1
num = first(num)
print(num)
return num
num = 0
num_result = main(num)
print(num_result)
The variable num is just initialized in the main-method, you're not initializing it in the first()-Method. So you've to hand it over to the first()-Method.
def first(num):
return num + 1
def main():
num = 1
num = first(num)
return num
num = 0
num_result = main()
print(num_result)

Why can only some functions use variables declared outside of the function?

I have this code:
def r():
i += 1
return i
def f():
return x*a
i = 0
a=2
x=3
print f()
print r()
I get this error for r(), but not for f():
~$ python ~/dev/python/inf1100/test.py
6
Traceback (most recent call last):
File "/home/marius/dev/python/inf1100/test.py", line 18, in <module>
print r()
File "/home/marius/dev/python/inf1100/test.py", line 2, in r
i += 1
UnboundLocalError: local variable 'i' referenced before assignment
Why can f() use variables defined outside of the function, whilst r() cannot?
That's because r reassigns the global variable i. f on the other hand just uses it. Remember that i += 1 is the same as i = i + 1.
Unless you explicitly tell it otherwise, Python treats all variables used within a function as being local. Furthermore, since there is no variable i defined within the local scope of r, it throws the error.
If you want to reassign a global variable within a function, you have to put:
global var
at the top of your function to explicitly declare var to be global.
So, to make r work, it should be rewritten to this:
def r():
global i
i += 1
return i
I'd like to point out that most of the time, this:
x = 1
def f():
global x
x += 1
f()
is bad practice, and you want to use parameters instead:
x = 1
def f(a_number):
return a_number + 1
x = f(x)
Also, here:
def r():
global i
i += 1
return i
return i is redundant, the variable is increased by the calling of the function.
Also this part of the Python FAQ is relevant and useful.
This piece:
def r():
i += 1
return i
not only uses global variables, but also tries to modify them (or more accurately: assign different value to global variable i).
To make it work, you can just declare this variable as global:
def r():
global i
i += 1
return i
In r you are shadowing your global i. Since it is not assigned before you attempt to add to it, you get an error.
A possible solution is to use global i in the r function like so
def r():
global i
i += 1
return i
If you assign into a variable, python will consider the variable local and won't bother looking for a global variable of the same name. Use global as suggested in other answers.

Why doesn't this closure modify the variable in the enclosing scope?

This bit of Python does not work:
def make_incrementer(start):
def closure():
# I know I could write 'x = start' and use x - that's not my point though (:
while True:
yield start
start += 1
return closure
x = make_incrementer(100)
iter = x()
print iter.next() # Exception: UnboundLocalError: local variable 'start' referenced before assignment
I know how to fix that error, but bear with me:
This code works fine:
def test(start):
def closure():
return start
return closure
x = test(999)
print x() # prints 999
Why can I read the start variable inside a closure but not write to it?
What language rule is causing this handling of the start variable?
Update: I found this SO post relevant (the answer more than the question): Read/Write Python Closures
Whenever you assign a variable inside of a function it will be a local variable for that function. The line start += 1 is assigning a new value to start, so start is a local variable. Since a local variable start exists the function will not attempt to look in the global scope for start when you first try to access it, hence the error you are seeing.
In 3.x your code example will work if you use the nonlocal keyword:
def make_incrementer(start):
def closure():
nonlocal start
while True:
yield start
start += 1
return closure
On 2.x you can often get around similar issues by using the global keyword, but that does not work here because start is not a global variable.
In this scenario you can either do something like what you suggested (x = start), or use a mutable variable where you modify and yield an internal value.
def make_incrementer(start):
start = [start]
def closure():
while True:
yield start[0]
start[0] += 1
return closure
There are two "better" / more Pythonic ways to do this on Python 2.x than using a container just to get around the lack of a nonlocal keyword.
One you mentioned in a comment in your code -- bind to a local variable. There is another way to do that:
Using a default argument
def make_incrementer(start):
def closure(start = start):
while True:
yield start
start += 1
return closure
x = make_incrementer(100)
iter = x()
print iter.next()
This has all the benefits of a local variable without an additional line of code. It also happens on the x = make_incrememter(100) line rather than the iter = x() line, which may or may not matter depending on the situation.
You can also use the "don't actually assign to the referenced variable" method, in a more elegant way than using a container:
Using a function attribute
def make_incrementer(start):
def closure():
# You can still do x = closure.start if you want to rebind to local scope
while True:
yield closure.start
closure.start += 1
closure.start = start
return closure
x = make_incrementer(100)
iter = x()
print iter.next()
This works in all recent versions of Python and utilizes the fact that in this situation, you already have an object you know the name of you can references attributes on -- there is no need to create a new container for just this purpose.
Example
def make_incrementer(start):
def closure():
# I know I could write 'x = start' and use x - that's not my point though (:
while True:
yield start[0]
start[0] += 1
return closure
x = make_incrementer([100])
iter = x()
print iter.next()
In Python 3.x you can use the nonlocal keyword to rebind names not in the local scope. In 2.x your only options are modifying (or mutating) the closure variables, adding instance variables to the inner function, or (as you don't want to do) creating a local variable...
# modifying --> call like x = make_incrementer([100])
def make_incrementer(start):
def closure():
# I know I could write 'x = start' and use x - that's not my point though (:
while True:
yield start[0]
start[0] += 1
return closure
# adding instance variables --> call like x = make_incrementer(100)
def make_incrementer(start):
def closure():
while True:
yield closure.start
closure.start += 1
closure.start = start
return closure
# creating local variable --> call like x = make_incrementer(100)
def make_incrementer(start):
def closure(start=start):
while True:
yield start
start += 1
return closure

Categories

Resources