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

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.

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.

Why can I edit object by calling their methods but not referencing their variable names from within a smaller scope

My main() function is:
def main():
...
def _help():
...
1 a += somelist
2 a.append(something)
a=[]
_help()
What's weird is that line 2 works perfectly fine, but line 1 throws an UnboundLocalError: Local variable 'a' referenced before assignment.
Even when I declare a as a global variable at the top of either main or _help, it still doesn't work. Why is this?
Both of these lines are editing the same variable which makes me think either both or neither of them should work. How do I get line 1 to work?
Whenever you use <variable> = <something> in Python, Python automatically assumes it is a local variable, unless specifically told otherwise.
For example:
a = 1
def f():
if False:
a = 0
print(a) # UnboundLocalError
f()
In this case, += works as assignment as well, but .append does not assign to a, but calls a method.
This is fixed by placing a nonlocal a in your function, so it can assign to the a outside of its scope:
def main():
...
def _help():
nonlocal a
a += somelist # Works!
But in this case, you can just do a.extend(somelist).

Differences between types in Python and their visibility

I just wondering, what differences between next two functions (Python 3.x)
def first_example ():
counter = 0
def inc_counter ():
counter += 1
for i in range (10):
inc_counter ()
def second_example ():
counter = [0]
def inc_counter ():
counter[0] += 1
for i in range (10):
inc_counter ()
First function throw exception about referenced before assignment, but the second function works well. Could somebody explain me, why python remember arrays, but not integers?
You are assigning to the counter name in the first nested function, making it a local variable.
In the second example, you never assign to counter directly, making it a free variable, which the compiler rightly connected to the counter in the parent function. You never rebind the name, you are only altering the list counter refers to.
Use the nonlocal keyword to mark counter as a free variable:
def first_example ():
counter = 0
def inc_counter ():
nonlocal counter
counter += 1
for i in range (10):
inc_counter()
Variables captured by inner functions (aka "closures") can't be assigned to, unless you explicitly mark them global or nonlocal.
In the first example, since you're trying to assign, counter is taken to refer to an inner local variable, not the outer variable. This inner variable is referenced before assignment, which is an error.
In the second example you are not assigning to the variable, you're calling __getitem__ on it to do the index lookup. So that's OK, counter refers to the outer variable.
In Python, a variable is created the first time you assign to it within a function. In your first example, counter is assigned inside inc_counter, since writing
counter += 1
is like writing
counter = counter + 1
so a new variable called counter is created within that scope.
In your second example, you are only accessing the counter variable, not assigning it, so no new variable is created.
In the first example, your intent is to change counter to point to a different object (the integer 1 since it starts at 0).
In the second example, counter remains pointing to the same object: the list. An element of the list is changed. No assignment to counter occurs.
The first is not allowable without (in Python 3) declaring counter nonlocal. In Python 2 it is not allowable in any circumstances. The assignment += causes counter to be a local variable to the inner function. Since it hasn't been assigned a value in that scope, attempting to use it (as += also does) causes Python to complain about that.

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.

UnboundLocalError with nested function scopes [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.

Categories

Resources