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.
Why is the following code invalid:
def foo1(x=5):
def bar():
if x == 5:
x = 6
print(x)
bar()
While this code is valid:
def foo2(x=5):
def bar():
if x == 5:
print('ok')
print(x)
bar()
foo2() will do exactly what you expect, but foo1() will give a UnboundLocalError: local variable 'x' referenced before assignment at the line if x == 5:. Why does altering the value of x later on in the code make this conditional invalid?
Python needs first to detect what variables are local, and which variable are fetched from an outer scope. In order to do that it looks for assignments, like:
def foo1(x=5):
def bar():
if x == 5:
x = 6 # an assignment, so local variable
print(x)
bar()
The point is, that the assignment can happen anywhere. For instance on the last line. Nevertheless, from the moment there is an assignment somewhere x is local. So in your first code fragment, x is a local variable. But you fetch it before it is assigned (bounded), so Python will error on it.
In python-3.x you can use the nonlocal keyword to access x from an outer scope:
def foo1(x=5):
def bar():
nonlocal x
if x == 5:
x = 6
print(x)
bar()
For python-2.x, you can for instance assign the variable to the function, like:
def foo1(x=5):
def bar():
if bar.x == 5:
bar.x = 6
print(bar.x)
bar.x = x
bar()
Note however that the two are not equivalent. Since in the former if you alter x, it will be alter the x in the foo1 scope as well. In the latter example you only modify bar.x. Of course if these are mutable objects, you alter the same object.
Related
Code extract is below, I am trying to modify a variable in a nested function, I started out by practising modifying in a normal function and using the keyword 'global', however in a nested function I can only make it work by putting in global (highlighted in comment), but by doing this I am expanding the scope of the 'y' var outside the function bar, to the main program which is not what I want.
I only want the inner function foo to be able to see and modify 'y' but I do not want to make 'y' available to the main program.
Note I have already seen this post Using a global variable inside a function nested in a function in Python, however the question remains.
Thank you.
# accessing x outside scope is okay
x = 5
def bar():
print(x)
bar()
# modifying x outside scope needs global
x = 5
def bar():
global x
x = x+5
print(x)
# now original reference has been changed as well.
bar()
print(x)
# scope within nested functions
def bar():
global y ## why is this needed?
y = 5
print(f'y before foo called (): {y}')
def foo():
global y
y = y + 1
print(f'y in foo called (): {y}')
foo()
print(f'y after foo called (): {y}')
bar()
print(f'y outside function foo called (): {y}')
Your solution works because global y makes y global. It is now available in all functions, including in nested functions. Instead, you probably want to declare nonlocal y inside foo() and remove all the global y declarations. This allows you to assign a value to y in the nested scope without exposing it to other functions.
This question already has answers here:
In Python, variables inside if conditions hide the global scope even if they are not executed?
(4 answers)
Closed 4 years ago.
Today I'm reading python change log and meet the nonlocal keyword and did some experiment with it. I find a confusing situation where untriggered assignment will change the nonlocal keyword behavior, please see the example below.
def a():
x = 'a'
def b():
def c():
nonlocal x
x = 'c'
c()
b()
print(x)
a()
>>> python3 test.py
c
def a():
x = 'a'
def b():
def c():
nonlocal x
x = 'c'
c()
if False:
x = 'b'
b()
print(x)
a()
>>> python3 test2.py
a
You can saw that in test2.py, there is an untriggered assginment x = 'b' which changed the behavior of nonlocal.
Why this happened?
Python decides which variables are local to a function at compile time. x is assigned to within the function b, so it's local. That that branch is never actually reached at runtime is irrelevant and can't be decided in general.
So the x in c that is nonlocal is the next x in an outer scope, namely the one in b.
The alternative would be much more surprising -- consider what would happen if the if False: was instead if rand(10) == 6:. Then during the first call of b the nonlocal variable would refer to the outermost one, but randomly at some later call of b it would start referring to the other one!
This question already has an answer here:
Nested function definitions and scope (UnboundLocalError) [duplicate]
(1 answer)
Closed 4 years ago.
def foo():
m = 3
def bar():
print(m) # code 1
m=4 # code 2
bar()
foo()
UnboundLocalError: local variable 'm' referenced before assignment
Why do I get the UnboundLocalError? I know bar can't change the value of m, but shouldn't barjust be able to get the value of m?
And when I try the code 1/code 2 separately, it all works.
Since the inner function bar has an assignment m=4, m is considered as a local variable, for whole of the function. But at the point you invoke print(m), m is not yet created. So you get the error UnboundLocalError: local variable 'm' referenced before assignment.
In Python 3, you can fix the code by declaring m to be nonlocal in the inner scope. This avoids placing m in the global scope (which is also an option in both Python 2 and 3 using the global keyword in place of nonlocal). The following code works in Python 3:
def foo():
m = 3
def bar():
nonlocal m
print(m) # code 1
m=4 # code 2
bar()
foo()
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 8 years ago.
I need to create program code in python that uses a defined variable from a different subprogram using a simple version:
x = 'ham'
def a():
x = 'cheese'
def b():
print(x)
a()
b()
How do I get this to save the global variable x as cheese instead of ham?
Whenever you mutate a global variable in a function, you have to explicitly declare that you're using it as a global:
x = 1
def a():
global x
x = 4
def b():
print(x)
a()
b()
Otherwise, a just creates a local variable x that shadows the global.
I'm guessing by subprogram you mean function?
The reason you are getting a 1 instead of a 4 is because x = 1 sets a global variable (in the global scope).
When you do x = 4 inside of a function it creates a local variable (in that function's local scope). Once the function is finished, the local variables are discarded.
When you call b() and it tries to look up the value of x, there is no local variable x (in b's local scope) so it uses the global variable x, which is 1.
If you want a() to modify the global variable x, you have to options:
1) You can just modify the global variable explicitly
def a():
global x
x = 4
2) You can return the local varaible and assign it to the global (preferred)
def a():
x = 4
return x
x = a()
TL;DR: I want a locals() that looks in a containing scope.
Hi, all.
I'm teaching a course on Python programming to some chemist friends, and I want to be sure I really understand scope.
Consider:
def a():
x = 1
def b():
print(locals())
print(globals())
b()
Locals prints an empty environment, and globals prints the usual globals. How do I get access to the environment where x is stored? Clearly the interpreter knows about it because I can refer to it.
Related: When does scoping happen? The following nameErrors on a = x+2 only if x=3 is included:
def a():
x = 1
def b():
a = x+2
x = 3
b()
If you comment out x=3, the code works. Does this mean that python makes a lexical-scope pass over the code before it interprets it?
What is happening in your code is that when python see's the x=3 line in your b() method, it is recreating x with a scope within the b function instead of using the x with it's scope in the a function.
because your code then goes:
a = x+2
x = 3
it is saying that you need to define the inner scoped x before referencing it.
however, when you do not have the assigning of x within the b function python does not attempt to make a lower scoped x and will not throw any errors.
The following code will illustrate this:
def a():
x = 1
def b():
x = 3
print (x)
def c():
print (x)
b()
c()
print (x)
a()
However if you were to declare x as global you could use it within a function, like so:
def a():
global x
x = 1
def d():
global x
x += 2
print (x)
d()
print (x)
a()
Python 3 has also added a nonlocal keyword that will let you access a variable from an enclosing scope, usage is like:
def a():
x = 1
def d():
nonlocal x
x += 2
print (x)
d()
print (x)
a()
Will print the same results as the global example.
In case I miss-read the question:
As per the answer to Get locals from calling namespace in Python:
you can use:
import inspect
def a():
x = 1
def d():
frame = inspect.currentframe()
try:
print (frame.f_back.f_locals)
finally:
del frame
d()
a()
to get the local scope of the functions caller.
The statement print(locals()) refers to nearest enclosing scope, that is the def b(): function. When calling b(), you will print the locals to this b function, and definition of x is outside the scope.
def a():
x = 1
def b():
print(locals())
print(globals())
b()
print(locals())
would print x as a local variable.
For your second question:
def a():
x = 1
def b():
#a = x+2
x = 3
b()
>>> a()
gives no error.
def a():
x = 1
def b():
a = x+2
#x = 3
b()
>>> a()
gives no error.
And
def a():
x = 1
def b():
a = x+2
x = 3
b()
>>> a()
gives following error: UnboundLocalError: local variable 'x' referenced before assignment
IMO (please check), in this third case, first statement in the body of b() looks for value of x in the most enclosing scope. And in this scope, x is assigned on the next line. If you only have statement a = x+2, x is not found in most enclosing scope, and is found in the 'next' enclosing scope, with x = 1.
python3 -c "help(locals)" says this about the locals function:
locals()
Return a dictionary containing the current scope's local variables.
That means that calling locals() in b() will only show you which variables are local to b(). That doesn't mean that b() can't see variables outside its scope -- but rather, locals() only returns information for variables local to b().
What you probably want is to make b() show information on the variables of the calling function. To do that, take this code (which show's b()'s local variables):
def a():
x = 1
def b():
print(locals())
b()
and replace it with this one (which uses the inspect module to get you the local information of the calling frame):
def a():
x = 1
def b():
import inspect
print(inspect.currentframe().f_back.f_locals)
b()
As for your other issue:
def a():
x = 1
def b():
a = x+2
x = 3
b()
The error you get is because Python 3 allows you to set/assign global variables and variables local to outside scopes provided you declare them with the global and nonlocal keywords. (I won't explain them here; I'll just say that you won't have any problem looking them up yourself.)
If you remove the x = 3 line (keeping the a = x+2 line), your code will run because x's value is being used, but not set. If instead you remove the a = x+2 line (keeping the x = 3 line), your code will run because it will create a new x inside b()'s scope.
If you keep both lines, Python doesn't know whether x is supposed to refer to an x outside the current scope (as a = x+2 seems to suggest) or if it's supposed to refer to an x local to b()'s scope (as x = 3 suggests).
If you want x to be local to b(), then you shouldn't have that a = x+2 line there, at least not before x is set with x = 3. But if you want x to be a()'s x, then you should declare x as nonlocal to b(), like this:
def a():
x = 1
def b():
nonlocal x # use a()'s x
a = x+2
x = 3
b()
If you're confused, remember that Python will let you use global variables and variables created outside of your current scope, but only if you don't assign to them. (It's often acceptable to read global variables, but it's frowned upon to set them.)
If you want to set a global variable or a non-local variable, you will have to declare those variables with global (for global variables) or nonlocal (for non-local variables), as shown in the above code.
I hope this helps.