Variable scope in nested functions

Can someone explain why the following program crashes:

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 just 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 exit

 10 11 12 13 14 15 16 17 18 19 

I got confused that if from f() it cannot access x , why does it become available if x is an array?

Thanks.

+6
source share
4 answers

But this answer suggests that the problem is with the purpose of x. If this is the case, printing should work fine, right?

You must understand the order in which everything happens. Before your python code is even compiled and executed, something called a parser reads the python code and checks the syntax. Another thing the parser does is variable labels as local ones. When the analyzer sees the assignment in the code in the local area, the variable on the left side of the assignment is marked as local. At this point, nothing was compiled yet - not to mention execution, and therefore the appointment does not occur; the variable is simply marked as a local variable.

Upon completion of the analyzer, 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 as if it should go ahead and print x in the scope (β€œE” in the LEGB search process). However, since the parser previously marked as a local variable inside f (), python does not go past the local area (β€œL” in the LEGB search process) to search for x. Since x was not assigned in the local area at the time "print x" was executed, python spits out an error.

Note that even if the code in which the assignment occurs is NEVER executed, the parser still marks the variable to the left of the destination as a local variable. The parser has no idea how everything will be executed, so it blindly searches for syntax errors and local variables in your file - even in code that can never be executed. Here are some examples of this:

 def dostuff (): x = 10 def f(): print x if False: #The body of the if will never execute... abc #...yet the parser finds a syntax error here return f f = dostuff() f() --output:-- File "1.py", line 8 abc ^ SyntaxError: invalid syntax 

The parser does the same 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 let's see what happens when you run this 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 expression "print x" is executed, since the parser marked as a local variable searches for x in the local scope.

This function is not unique to python - it happens in other languages.

As for the array example, when you write:

 x[0] = x[0] + 1 

which tells python to go to an array named x and assign it to the first element. Since no x name is assigned in the local scope, the analyzer does not mark x as a local variable.

+4
source

In the first example, you used the assignment operation, x = x + 1 , so when the functions were defined, python thought x a local variable. But when you actually called the python function, you could not find any value for x on RHS locally, So raised an error.

In your second example, instead of assigning, you just changed the mutable object, so python will never raise objections and extract the value x[0] from the enclosing scope (in fact, it first looks for it in the enclosing scope, then the global scope, and finally in the embedded ones. but stops as soon as it is found).

In python 3x you can handle this with a non-local keyword, and in py2x you can pass a value to an internal 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 
+3
source

The problem is that the variable x captured by the closure. When you try to assign a variable obtained from closure, python will complain if you don't use the global or nonlocal 1 nonlocal . In the case when you use list , you do not assign a name - you can change the object that was obtained in the closure, but you cannot assign it.


Basically, the error occurs on the print x line, because when python parses this function, it sees that x is assigned so that it is assumed that x should be a local variable. When you get to the line print x , python will try to find the local x , but it is not there. This can be seen using dis.dis to check for byte code. Here python uses the LOAD_FAST , which is used for local variables, and not the LOAD_GLOBAL , which is used for non-local variables.

This usually raises a NameError , but python tries to be a little more useful by looking for x in func_closure or func_globals 2 . If it finds x in one of them, an UnboundLocalError occurs in its UnboundLocalError to give you a better idea of ​​what is going on. You have a local variable that cannot be found (not "bound").

1 only python3.x

2 python2.x - On python3.x, these attributes changed to __closure__ and __globals__ respectively

+1
source

The problem is the line

 x = x + 1 

For the first time, x is assigned in the f() function, telling the compiler that x is a local name. It conflicts with the previous line of print x , which cannot find any previous destination of local x . Where your error comes from UnboundLocalError: local variable 'x' referenced before assignment .

Note that an error occurs when the compiler tries to figure out which object x refers to in print x . Thus, print x not executed.

Change it to

 x[0] = x[0] + 1 

No new name. Therefore, the compiler knows that you are referencing an array outside of f() .

0
source

Source: https://habr.com/ru/post/944585/


All Articles