Python scope when assignment never performed - python

So I get that
x = 5
def f():
print(x)
f()
print(x)
gives back 5 and 5.
I also get that
x = 5
def f():
x = 7
print(x)
f()
print(x)
gives back 7 and 5.
What is wrong with the following?
x = 5
def f():
if False:
x = 7
print(x)
else:
print(x)
f()
print(x)
I would guess that since the x=7 never happens I should get 5 and 5 again. Instead I get
UnboundLocalError: local variable 'x' referenced before assignment
Does python regard x as a local variable because in this indented block there is an assignment expression regardless if it is executed or not? What is the rule exactly?

When the function is defined python interprets x as a local variable since it's assigned inside the body of the function. During runtime, when you go into the else clause, the interpreter looks for a local variable x, which is unassigned.
If you want both x to refer to the same variable, you can add global x inside the body of the function, before its assignment to essentially tell python when I call x I'll be referring to the global-scope x.

If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.

You need to use global in your function f() like so:
x = 5
def f():
global x
if False:
x = 7
print(x)
else:
print(x)
f()
print(x)

Related

Why is global keyword not ignored even if it is unreachable? (Python)

I am really new to programming and Python, so please really forgive me for my ignorance.
I just learned that using global can make a variable inside function be a global one.
However, I discovered something not with my expectation:
I tried the following code in Python 3.8 (forgive me for my ignorance as I don't know what else information I should provide):
>>> x = 0
>>>
>>> def function():
... if False:
... global x
... x = 1
...
>>> function()
>>> print(x)
and the result is 1.
However, I expected the code to have the same effect as the following code:
>>> x = 0
>>>
>>> def function():
... x = 1
...
>>> function()
>>> print(x)
which the result should be 0.
In my mind, the statement inside if False should not be executed, so it sounds strange to me.
Also, personally, I think that in some situation I would expect the variable inside a function, whether local or global, to be dependent on other codes... what I mean is, I would like to change if False to something like if A == 'A', while (I hope) I can control whether the x is global/local according to my conditional statement.
I tried to change if to while, but it's the same... there isn't a infinite loop, but the code global x is still executed/compiled...
I admit that it may sounds naive, and perhaps it just won't work in Python, but I really wonder why... It seems that the code global x is unreachable, but how come it is not ignored?
Can anyone please tell me about the reason? I would like to know more about the mechanism behind compilation(?)
Any help would be appreciated, thank you!
In python the global statement (and the nonlocal statement) are very different from the normal python code. Essentially no matter where a global statement in a function is, it influences always the current codeblock, and is never "executed". You should think more of it as a compiler directive instead of a command.
Note that the statement itself must come before any usage of the variable it modifies, i.e.
print(x)
global x
is a syntax error. The global statement can only modify variable behavior in the whole codeblock, you can't first have a non-global variable that later gets global and you can also not have conditional global variable
(I couldn't really find good documentation for this behavior, here it says "The global statement is a declaration which holds for the entire current code block." and "global is a directive to the parser. It applies only to code parsed at the same time as the global statement." but that doesn't seem super clear to me.)
There are more compiler directives in python, although they don't always look like one. One is the from __future__ import statements which look like module imports but change python behavior.
Global is not in execution path but in a scope. The scope is whole function. Statements like if for don't make scopes. If you use any assignment you create local variable. The same with global or nonlocal you bind symbol to variable from outside.
As Stanislas Morbieu typed, see doc.
Programmer’s note: global is a directive to the parser. It applies only to code parsed at the same time as the global statement.
Not at execution time.
x = 1
def fun():
y = x + 1
print(f'local x = {x}, y = {y}')
fun()
print(f'global x = {x}')
# Output:
# local x = 1, y = 2
# global x = 1
In example above, y uses global x (and adds 1).
x = 1
def fun():
y = x
x = y + 1
print(f'local x = {x}')
fun()
print(f'global x = {x}')
# Output:
# UnboundLocalError: local variable 'x' referenced before assignment
Look at last example. It doesn't assign y from global x because assignment in second line creates local x and y can not read local x before x assignment. The same with:
x = 1
def fun():
if False:
x += 1
fun()
# Output
# UnboundLocalError: local variable 'x' referenced before assignment
x assignment creates local variable.
If you want to change global variable under condition you can use globals().
x = 1
def set_x(do_set, value):
if do_set:
globals()['x'] = value
print(f'global x = {x} (init)')
set_x(False, 2)
print(f'global x = {x} (false)')
set_x(True, 3)
print(f'global x = {x} (true)')
# Output
# global x = 1 (init)
# global x = 1 (false)
# global x = 3 (true)
Proxy
I you want to decide with variable you want to use later (in the same scope) you need some kind of proxy IMO.
x = 1
def fun(use_global):
x = 2 # local
scope = globals() if use_global else locals()
scope['x'] += 1
print(f'local ({use_global}): x = {scope["x"]}')
print(f'global: x = {x} (init)')
fun(False)
print(f'global: x = {x} (false)')
fun(True)
print(f'global: x = {x} (true)')
# Output:
# global: x = 1 (init)
# local (False): x = 3
# global: x = 1 (false)
# local (True): x = 2
# global: x = 2 (true)
Maybe you can think about refactoring of your code if you need it.
If you can change local variable name (if not use globals() as above), you can proxy:
use dict (like in example above)
use list (x=[1]) and usage x[0]
use object (with builtin dict), example:
class X:
def __init__(self, x):
self.x = x
x = X(1)
def fun(use_global):
global x
my_x = x if use_global else X(2)
my_x.x += 1
print(f'local ({use_global}): x = {my_x.x}')
print(f'global: x = {x.x} (init)')
fun(False)
print(f'global: x = {x.x} (false)')
fun(True)
print(f'global: x = {x.x} (true)')
# Output:
# global: x = 1 (init)
# local (False): x = 3
# global: x = 1 (false)
# local (True): x = 2
# global: x = 2 (true)
Note. Variables in Python are only references. It is way you can not change x = 1 without global (or globals()). You change reference to local value 1.
But you can change z[0] or z['x'] or z.x. Because z referents to list or dict or object and you modify it content.
See: https://realpython.com/python-variables/#object-references
You can check real object by id() function, ex. print(id(x), id(my_x)).
As per the Python documentation, global is a directive to the parser so it is taken into account before the execution, therefore it does not matter if the code is reachable or not. The variable is global for the entire scope, which is the function in your case.

nonlocal statement got checked without running the function in python [duplicate]

This question already has answers here:
When is the existence of nonlocal variables checked?
(2 answers)
Closed 4 years ago.
previously I thought that when we define a function, the function can be wrong, but python will not check it until it got executed:
x = 100
def f():
x = 1/0
return x
print(x)
# >>> 100
however, when I was learning the nonlocal statement
x = 100
def f():
def g():
nonlocal x
x = x * 99
return x
return g
print(x)
# >>> SyntaxError: no binding for nonlocal 'x' found
It got checked out even if the function is not executed.
Is there anywhere I can find the official explanation for this?
Additional for variable bounding situation:
x = 100
def f():
global x
global xx
x = 99
return x
print(f())
# >>> 99
print(x)
# >>> 99
it seemed totally fine, if I global some variable that does not exist at all?
And it doesn't even bring any error even if I execute this function?
this part is moved to a new individual question:
Why am I able to global a non-existing varlable in python
The nonlocal checks the nearest enclosing scope, excluding globals (that is, module level variables). That is, your f() function should declare a x for it to work, as nonlocal can't see the global x = 100 variable.
See https://docs.python.org/3/reference/simple_stmts.html#the-global-statement.
As to why the error is raised without running the function, it is because the variable names are bound at compile-time, so it doesn't matter that you don't use this function at all. See
https://docs.python.org/3/reference/executionmodel.html#resolution-of-names
However, global has different behaviour. Just like nonlocal if the global x already exists, it is used instead of the local one. However, if it doesn't, it means "if I create a variable named x, create it on the global scope, instead of the function scope". So, in your example
x = 100
def f():
global x
global xx
x = 99
xx = 123
return x
print(f()) # 99
print(x) # 99
print(xx) # 123
a xx variable has appeared in the global namespace. It is only a hint to the compiler though, so if you declared global xx without assigning it, and try to print(xx) later, you still get a NameError for using an undefined variable

Understanding UnboundLocalError in Python

Hello I am newbie to Python. While learning I came across below code snippets in that I am unable to understand behaviour of the code.
Here is first case
#Case 1
x=1
def func():
x=5
print(x)
func()
5
Here is second case
#Case 2
x=1
def func():
print(x) #First print statement
x=5
print(x) #Second print statement
func()
UnboundLocalError: local variable 'x' referenced before assignment
The two cases are same except the second has one additional print statement. Why the first print statement of second snippet making the Python to throw an exception?
The x outside the function is there but x is also defined in both functions as a local variable and you must define it before using it. It doesn't mean that Python will use the x from outside the function first and then allow you to redefine x as a local variable and then use the local variable x in the rest of the function.
So the difference is really between:
def func():
x=5
print(x)
def func():
print(x) #First print statement
x=5
print(x) #Second print statement
In both functions x is a local variable but in the second function you're trying to use it before it's being defined in that function. Hence the error.

Python 3 syntax should work, but doesn't

It's so basic it should work. I want a function that adds a value to something
There must be something I don't know about python 3, so here we go.
x = 0
def foo(x=x): ##### with out x=x there is a error
x = x + 1 # option one
x = 1 # option two
# when we run it
foo()
print(x)
# it returns 0, it should return 1
x is a local variable in foo(); Assigning x as a default value for a keyword argument won't make it any less a local.
If you wanted it to be a global, mark it as such:
x = 0
def foo():
global x
x = x + 1
print(x)
foo()
print(x)
but you probably just wanted to pass in the value as an argument instead:
def foo(value):
return value + 1
x = 0
print(x)
x = foo(x)
print(x)
This is basically and example of scoping rules. The variable x within foo is local to foo, so nothing that happens to the local x changes anything outside foo, including the global x with which is actually a different variable. When the interpreter exits foo the global x comes back into scope and it hasn't changed from its initial value of 0. The function header foo(x=x) defines a local x whose default value is the global x. The interpreter allows it but it's generally considered bad programming practice to have the same variable name representing two variables because it leads to this kind of confusion.

Variable scope in nested functions

Could someone explain why the following program fails:
def g(f):
for _ in range(10):
f()
def main():
x = 10
def f():
print x
x = x + 1
g(f)
if __name__ == '__main__':
main()
with the message:
Traceback (most recent call last):
File "a.py", line 13, in <module>
main()
File "a.py", line 10, in main
g(f)
File "a.py", line 3, in g
f()
File "a.py", line 8, in f
print x
UnboundLocalError: local variable 'x' referenced before assignment
But if I simply change the variable x to an array, it works:
def g(f):
for _ in range(10):
f()
def main():
x = [10]
def f():
print x[0]
x[0] = x[0] + 1
g(f)
if __name__ == '__main__':
main()
with the output
10
11
12
13
14
15
16
17
18
19
The reason I am confused is, if from f() it can't access x, why it becomes accessible if x is an array?
Thanks.
But this answer says the problem is with assigning to x. If that's it,
then printing it should work just fine, shouldn't it?
You have to understand the order in which things happen. Before your python code is even compiled and executed, something called a parser reads through the python code and checks the syntax. Another thing the parser does is mark variables as being local. When the parser sees an assignment in the code in a local scope, the variable on the lefthand side of the assignment is marked as local. At that point, nothing has even been compiled yet--let alone executed, and therefore no assignment takes place; the variable is merely marked as a local variable.
After the parser is finished, the code is compiled and executed. When execution reaches the print statement:
def main():
x = 10 #<---x in enclosing scope
def f():
print x #<-----
x = x + 1 #<-- x marked as local variable inside the function f()
the print statement looks like it should go ahead and print the x in the enclosing scope (the 'E' in the LEGB lookup process). However, because the parser previously marked x as a local variable inside f(), python does not proceed past the local scope (the 'L' in the LEGB lookup process) to lookup x. Because x has not been assigned to in the local scope at the time 'print x' executes, python spits out an error.
Note that even if the code where an assignment occurs will NEVER execute, the parser still marks the variable on the left of an assignment as a local variable. The parser has no idea about how things will execute, so it blindly searches for syntax errors and local variables throughout your file--even in code that can never execute. Here are some examples of that:
def dostuff ():
x = 10
def f():
print x
if False: #The body of the if will never execute...
a b c #...yet the parser finds a syntax error here
return f
f = dostuff()
f()
--output:--
File "1.py", line 8
a b c
^
SyntaxError: invalid syntax
The parser does the same thing when marking local variables:
def dostuff ():
x = 10
def f():
print x
if False: #The body of the if will never execute...
x = 0 #..yet the parser marks x as a local variable
return f
f = dostuff()
f()
Now look what happens when you execute that last program:
Traceback (most recent call last):
File "1.py", line 11, in <module>
f()
File "1.py", line 4, in f
print x
UnboundLocalError: local variable 'x' referenced before assignment
When the statement 'print x' executes, because the parser marked x as a local variable the lookup for x stops at the local scope.
That 'feature' is not unique to python--it happens in other languages too.
As for the array example, when you write:
x[0] = x[0] + 1
that tells python to go lookup up an array named x and assign something to its first element. Because there is no assignment to anything named x in the local scope, the parser does not mark x as a local variable.
The reason is in first example you used an assignment operation, x = x + 1, so when the functions was defined python thought that x is local variable. But when you actually called the function python failed to find any value for the x on the RHS locally, So raised an Error.
In your second example instead of assignment you simply changed a mutable object, so python will never raise any objection and will fetch x[0]'s value from the enclosing scope(actually it looks for it firstly in the enclosing scope, then global scope and finally in the builtins, but stops as soon as it was found).
In python 3x you can handle this using the nonlocal keyword and in py2x you can either pass the value to the inner function or use a function attribute.
Using function attribute:
def main():
main.x = 1
def f():
main.x = main.x + 1
print main.x
return f
main()() #prints 2
Passing the value explicitly:
def main():
x = 1
def f(x):
x = x + 1
print x
return x
x = f(x) #pass x and store the returned value back to x
main() #prints 2
Using nonlocal in py3x:
def main():
x = 1
def f():
nonlocal x
x = x + 1
print (x)
return f
main()() #prints 2
The problem is that the variable x is picked up by closure. When you try to assign to a variable that is picked up from the closure, python will complain unless you use the global or nonlocal1 keywords. In the case where you are using a list, you're not assigning to the name -- You can modify an object which got picked up in the closure, but you can't assign to it.
Basically, the error occurs at the print x line because when python parses the function, It sees that x is assigned to so it assumes x must be a local variable. When you get to the line print x, python tries to look up a local x but it isn't there. This can be seen by using dis.dis to inspect the bytecode. Here, python uses the LOAD_FAST instruction which is used for local variables rather than the LOAD_GLOBAL instruction which is used for non-local variables.
Normally, this would cause a NameError, but python tries to be a little more helpful by looking for x in func_closure or func_globals 2. If it finds x in one of those, it raises an UnboundLocalError instead to give you a better idea about what is happening -- You have a local variable which couldn't be found (isn't "bound").
1python3.x only
2python2.x -- On python3.x, those attributes have changed to __closure__ and __globals__ respectively
The problem is in the line
x = x + 1
This is the first time x being assigned in function f(), telling the compiler that x is a local name. It conflicts with the previous line print x, which can't find any previous assignment of the local x.
That's where your error UnboundLocalError: local variable 'x' referenced before assignment comes from.
Note that the error happens when compiler tries to figure out which object the x in print x refers to. So the print x doesn't executes.
Change it to
x[0] = x[0] + 1
No new name is added. So the compiler knows you are referring to the array outside f().

Categories