Whenever you 'refer' to a name in Python, you are implicitly performing an unqualified name lookup. These follow the LEGB rule: the system looks for bindings of the name in different places in the order
- In the Local scope of the function in which the lookup is being done (if any)
- In the local scope of functions Enclosing the one in which the lookup is being done (if any)
- In the Global scope
- In the Builtins module.
The search stops as soon as it finds a binding of the name you seek. If the name is bound in more than one of those places, the earlier ones in the list are said to shadow the later ones.
In your first code sample, x
is bound in E/f
and y
is bound in L/g
. Both are parameters of functions, and function parameters are bound in the local scope of the function.
In your second sample x =
binds the name x
in the Local scope of the inner function g
, thereby shadowing the binding of x
in the Eenclosing function f
. Because of this, LEGB no longer finds x
in E/f
but in L/g
.
By seeing the x =
written inside g
Python decides at compile time that x
's binding comes from g
. Unfortunately, at runtime the binding of x
in g
has not been established yet. Which is why you get an UnboundLocalError
.
In your third example, exactly the same problem occurs: On line 4 the compiler discovers that there is a binding of x
in L/g
, which shadows the binding of x
in E/f
, and therefore the binding of x
in g
will be used at runtime. At runtime we get to line 3 (where the name is looked up) before line 4 (where the name is bound), so once again UnboundLocalError
.
The error is at line 3, because that is the line on which the lookup fails.
In summary, there is nothing wrong with assigning to x
in g
. The problem occurs when you try to lookup the value of x
in g
before it has been established.
If you are in Python 3, you could add the declaration nonlocal x
inside the inner function g
. This means: Dear compiler, if anywhere in this scope you find code that attempts to bind x
, do not create a binding in this scope, instead, rebind the name in whatever enclosing scope has already provided the binding.