A function defined within another function can access its parent's scope.
In your specific case, L
is always defined within foo()
. On the first two examples, bar()
is defined within foo()
as well, so it can access L
by the rule above (ie, foo()
is bar()
's parent).
However, on broken()
, bar()
and foo()
are siblings. They know nothing of each others' scopes, so bar()
cannot see L
.
From the documentation:
Although scopes are determined statically, they are used dynamically. At any time during execution, there are at least three nested scopes whose namespaces are directly accessible:
- the innermost scope, which is searched first, contains the local names
- the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contains non-local, but also non-global names
- the next-to-last scope contains the current module’s global names
- the outermost scope (searched last) is the namespace containing built-in names
Now, why does okay1
work, if L
is defined textually after bar()
?
Python does not try to resolve the identifiers until it has to actually run the code (dynamic binding, as explained in @Giusti's answer).
When Python gets to execute the function, it sees an identifier L
and looks for it on the local namespace. On the cpython implementation, it is an actual dictionary, so it looks on a dictionary for a key named L
.
If it does not find it, it checks on the scopes of any enclosing functions, ie the other dictionaries representing the local namespaces of the enclosing functions.
Note that, even if L
is defined after bar()
, when bar()
is called, L
has already been defined. So, when bar()
is executed, L
already exists on the local namespace of foo()
, which is searched when Python does not see L
within bar()
.
Supporting piece of the documentation:
A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries, but that’s normally not noticeable in any way (except for performance), and it may change in the future.
(...)
The local namespace for a function is created when the function is called, and deleted when the function returns or raises an exception that is not handled within the function. (Actually, forgetting would be a better way to describe what actually happens.) Of course, recursive invocations each have their own local namespace.
A scope is a textual region of a Python program where a namespace is directly accessible. “Directly accessible” here means that an unqualified reference to a name attempts to find the name in the namespace.