Nested Function Scope [duplicate] - python

This question already has answers here:
UnboundLocalError with nested function scopes [duplicate]
(3 answers)
Closed 2 years ago.
def foo():
a = 0
def bar():
a += 1
bar()
foo()
Why is it that this code does not execute when a is an immutable type, but does execute when it is mutable (a list, for example)?

I don't think the other answers narrate the entire story, so here's my 2 cents.The scope does not differ between types. That's not what's happening here. Rebinding a name, regardless of what that name refers to, will always cause an unbound local error to occur.
Some types in Python are mutable (once created, a value can be changed), and list is one of them. Some are immutable, and changing a value of these types requires creating a new object.
So, with the first example,
def foo():
a = 0
def bar():
a += 1
bar()
foo()
This doesn't work because you effectively have an assignment taking place here, a = a + 1. You can use non-local to make the above work, that being besides the point.
Doing the same thing with a list:
def foo():
a = []
def bar():
a.append(1)
bar()
foo()
This does indeed work. There is no assignment taking place here.
You can't re-bind the name to a different object, but if the object is mutable, you can modify its contents.
Now, there's 2 more cases you should be aware of.
def foo():
a = []
c = []
def bar():
a = c + [1]
bar()
print(a)
print(c)
foo()
This will work, however you should note that the a inside bar() now is local to bar, and the print() statement should reflect that.
But here's a gotcha
def foo():
a = []
def bar():
a = a + [1] #or even a += [1], doesn't matter
bar()
print(a)
foo()
This won't work! (And it's important you contrast this snippet with the first snippet, because that addresses why this has nothing to do with scopes. Take a minute to read it again.)
So this doesn't work and it's important to understand it.
If there is an assignment to a variable inside a function, that variable is considered local. Now, in the last case, when we did a = c + 1, it was more like a_local = c_nonlocal + 1.
In this case, a = a + 1 is a_local = a_local + 1 and hence, that will indeed cause an error. This is why, Rebinding a name, regardless of what that name refers to, will always cause an unbound local error to occur. In the case earlier(The third snippet), it wasn't rebinding it - it was creating a local variable. In the latter case(The fourth snippet), it was infact rebinding and hence the error.

There is a keyword nonlocal which is used to access nonlocal variables.
The nonlocal keyword is used to work with variables inside nested functions, where the variable should not belong to the inner function. --w3schools.com
def foo():
a = 0
def bar():
nonlocal a
a += 1
bar()
foo()
Thank you

That is because you are not returning any value from either functions and the second a is only local to the function bar().
Try using the nonlocal keyword like this:
def foo():
a = 0
def bar():
nonlocal a
a += 1
return a
return bar()
print(foo())
Output
1

Related

why is python function capable of mutating non-global list? [duplicate]

This question already has answers here:
Understanding Python's call-by-object style of passing function arguments [duplicate]
(3 answers)
Closed 6 years ago.
myList = [5, 2]
def foo():
myList[0] = 6
In the example above, myList is still mutated, though it is non-global and not passed in by parameter. This doesn't work however, if the variable is not a list.
The scope rules are roughly like this:
Local scope: you define the variable inside a function
Enclosing scope: your function is inside another function (or multiple layers of functions), and one of the higher (enclosing) functions declared the variable
Global scope: the global scope (module or file)
Built-in: Any built-in values native to Python
This is known as the LEGB rule.
In your case, it is because you are not assigning a value to the name myList inside of foo(). You are only assigning a value to the 0 index of an existing list. Therefore the global value of myList is the scope you are using.
myList is a global variable, though. It exists in the context of foo, where you access and modify one of its elements.
myList = [0, 5]
def foo():
myList[0] = 6
print('Before foo: {}'.format(myList))
foo()
print('After foo: {}'.format(myList))
Output
Before foo: [0, 5]
After foo: [6, 5]
This doesn't work however, if the variable is not a list.
I assume that you tried something similar to the following:
a = 0
def bar():
a = 6
print('Before bar: {}'.format(a))
bar()
print('After bar: {}'.format(a))
Output
Before bar: 0
After bar: 0
In this case, you don't observe any change in the global variable a because within bar you're assigning to a new local variable a which shadows the global variable of the same name. You set the local variable a to 6, which has no effect as the variable is discarded as soon as the function ends.
You can show that even simple integers declared in the global scope can be accessed within function scopes by running something like the following. In this case, we specify that the function should modify an existing global variable b rather than assigning to a new local one.
b = 0
def baz():
global b
b = 6
print('Before baz: {}'.format(b))
baz()
print('After baz: {}'.format(b))
Output
Before baz: 0
After baz: 6

nested function change variable in an outside function not working [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 months ago.
def some_func(a):
def access_a():
print(a)
access_a()
outputs the value of a. However, if I want to change a in the nested function like this:
def some_func(a):
def change_a():
a += 1
print(a)
change_a()
it raises UnboundLocalError exception.
I know a is a nonlocal variable, but why can I access it without declaring nonlocal a?
Python scoping rules 101:
a name bound in a function body is considered local unless explicitely declared global (Python 2.x and 3.x) or nonlocal (Python 3.x only). This holds true whereever the assignment happens in the function's body. Trying to read a local variable before it's bound is of course an error.
if a name is read but not bound in a function's body, it will be looked up in enclosing scopes (outer function(s) if any then global scope). NB: functions arguments are de facto local names so they will never be looked up in enclosing scopes.
Note that a += 1 is mainly a shortcut for a = a + 1, so in your example a is local (bound in the function's body and not explicitely declared global or nonlocal), but you try to read it (the rhs of a = a+1) before it's bound.
In Python 3 you can solve this with a nonlocal statement:
>>> def outer(a):
... def change():
... nonlocal a
... a += 1
... print("before : {}".format(a))
... change()
... print ("after : {}".format(a))
...
>>> outer(42)
before : 42
after : 43
Python 2 doesn't have nonlocal so the canonical hack is to wrap the variable in a mutable container (typically a list but any mutable object would do):
>>> def outer(a):
... _a = [a]
... def change():
... _a[0] += 1
... print("before : {}".format(_a[0]))
... change()
... print ("after : {}".format(_a[0]))
...
>>> outer(42)
before : 42
after : 43
which is quite ugly to say the least.
Now while closures are quite handy, they are mostly the functional counterpart of objects : a way to share state between a set of functions while preserving encapsulation of this state, so if you find you have a need for a nonlocal variable perhaps a proper class might be a cleaner solution (though possibly not for your example that doesn't return the inner function but only uses it internally).
i have two solutions for you:
#first one:
# try with list, compound data types dict/list
def some_func(a):
def change_a():
a[0] += 1
print(a[0])
change_a()
some_func([1])
>>> 2
#second one
#reference pointer
from ctypes import *
def some_func_ctypes(a):
def change_a():
a[0] += 1
print a.contents, a[0]
change_a()
i = c_int(1)
pi = pointer(i)
some_func_ctypes(pi)
>>> c_int(2) 2
When you use the += operator, there is an assignment of a new value to a. This turns a to a local in the eyes of the interpreter.

Are lists implicitly 'global' inside of functions? [duplicate]

This question already has answers here:
Modify global list inside a function
(5 answers)
Closed 6 years ago.
I was experimenting with this code:
def a():
#global p
p.append(4);
d=9
p=[2,3];
d=8
a();
print p # ----> [2, 3, 4]
print d # ----> 8
The variable d value is not changed as I didn't use the global keyword. But the list p was modified in function even though I didn't use global. Are all lists global by default in functions?
The critical difference is the assignment here. You are fine calling methods on existing global objects, but you can't assign to them without calling them global. In your code, the name d is being reassigned to reference another value. If you changed p with assignment you'd have a similar result
def a():
p = [5, 7] # new local variable, doesn't change global
p.append(9) # doesn't change global p
This makes sense if you think about what happens when python encounters the name for the first time. In the function you've provided, python will see p.append and say "hm, I don't have a local by the name p, let me look in the enclosing scope." It sees the global p and uses that.
In the example I've shown, python will say "there's no explicit global so I assume this is supposed to be a new local variable." and create one.
Names in python are just references. If python followed the behavior you are expecting you'd need a global for every function you called, let me explain:
def a():
p.append(1) # I should need 'global p' to do this
This would mean if you had
def g():
...
def f():
g() # this would also need 'global g', otherwise how does it see g?
def f2():
global g
def g(): # changes the global g function
return 0

Must I always reference a variable as global in my function if I were to use it?

Firstly, it's related to this function:
a = 3
def b():
a = a+1
return a
returns an error.
Isn't it when the local environment of b couldn't find 'a', then it goes to the parent environment where b is defined, that is, where 'a' is found to be 3?
Why must I reference it with a global a in order for it to work? Meaning, do I have to do this only when the immediate parent environment is the global environment? Because the below function seems to be able to reference the parent environment:
def b():
def c():
print c
return c()
This is such a common issue that is is actually in the Python FAQ:
[...] when you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope. Since the last statement in foo assigns a new value to x, the compiler recognizes it as a local variable.
So, Python sees you're assigning to a local variable with a = .. and thinks, hey, a local variable, I'm fine with this. But, when it actually bumps into the content of that assignment (a + 1) and sees the same name a it complains because you're trying to reference it before assigning it.
This is why the following doesn't raise an error:
def b():
a = 20
a = a + 2
return a
You first make an assignment, a is treated as local to the function b and when a = a + 2 is encountered, Python knows a is a local variable, no confusion is present here.
In the other case you added:
def b():
def c():
print c
return c()
You are only referencing a name enclosed in the scope of function b, referencing. If you changed this to a similar assignment as before, you'll get the same UnboundLocalError:
def b():
def c():
c = c + 20
return c()
Because you are doing an assignment and the scope has to be clarified.
Your case:
a = 3
def b():
a = a + 1
return a
print(b())
Output:
Traceback (most recent call last):
File "./tests/test1.py", line 12, in <module>
print(b())
File "./tests/test1.py", line 9, in b
a = a + 1
UnboundLocalError: local variable 'a' referenced before assignment
When you do the assignement a = ... you have created always a local variable unless it has been declared global which is not the case. Local resolution takes precedence.
Your example would obviously work with:
a = 3
def b():
global a
a = a + 1
return a
print(b())
Which outputs:
4
But using globals, imho, should not be the way and hence passing a as a parameter to function b would be it:
a = 3
def b(a):
a = a + 1
return a
print(b(a))
Which agains produces the expected 4.
EDIT:
Following the edition in the OP: you can always "reference" the global scope (and parent scope). But if you make assignments they take place in the local scope.
If your code (which is wronly indented and contains a new c which isnt' anywhere) is meant to do this:
a = 3
def b():
def c():
print(a)
return c()
b()
It works ... and prints 3 because there is NO assignment and only referencing. As soon as you executing an assignment a would again be created in the local scope.
Assignment will create a new variable in Python.
Here may help you.

About the running result of the following code (Python, less than 10 lines) [duplicate]

This question already has answers here:
Local variables in nested functions
(4 answers)
Closed 8 years ago.
Given this piece of code(python)
s = [None]*10
def func():
for i in range(10):
def update():
print i,
s[i] = update
func()
for i in range(10):
s[i]()
why this result is ten 9s, instead of 0,1,2,3,...9 ?
btw, I've also print s[0]~s[9],which are 10 function addresses, and there are different from each other.
You've created a bunch of closures over i, but they are all sharing the same (final) value of i
You need to make a tiny modification like this
>>> s = [None]*10
>>> def func():
... for i in range(10):
... def update(i=i):
... print i,
... s[i] = update
...
>>> func()
>>> for i in range(10):
... s[i]()
...
0 1 2 3 4 5 6 7 8 9
#gnibbler's code will fix yours (so accept his answer if anyone's). As for why it is printing all 9's, look at your update function. You are (again, as #gnibbler mentioned) continuously returning the i that you defined in for i in range(10). Since in your original code you did not call the update method with a function argument, it simply prints the i that was defined in the func scope (a completely different i which will be 9 after the function completes).
To perhaps make it more clear, try changing the i in func to a completely different name - the result will be the same.
What's happening is the same as this:
>>> a = 5
>>> def foo(): print(a)
>>> foo()
5
>>> a = 10
>>> foo()
10
>>> a = 'fred'
>>> foo()
fred
And also the same as this:
>>> def bar(): return b
>>> bar()
Traceback (most recent call last):
File "<pyshell#30>", line 1, in <module>
bar()
File "<pyshell#29>", line 1, in bar
def bar(): return b
NameError: global name 'b' is not defined
>>> b = 3
>>> bar()
3
The variables you used inside a function aren't resolved until the function is called not when it is written. There's some magic, called closures, that means functions defined inside other functions (as your update functions are defined inside func) still have access to all the variables defined in the outer function - but they still don't actually get resolved until the function is called. So, by the time each of your update functions is called, i is 9.
Using default argument values, as in #gnibbler's answer, works because the i that each update looks up will resolve to the argument (which shadows the outer variable). Those won't change, because default argument values are evaluated when the function is defined (which also leads to the mutable defaults bug that a lot of people run into sooner or later).

Categories