When we use a variable name, how does Python know where to find its value? It uses the LEGB rule!
LEGB stands for Local, Enclosing, Global, Built-in. These are the possible scopes of a variable in Python. We also use these words to describe namespaces, which store variable names and values and are how Python determines scope.
The order Local, Enclosing, Global, Built-in is the order of precedence for scope resolution. That means Python searches for the value of a name in each of these namespaces in turn.
In Python and other languages, variables and are stored in namespaces. A namespace maps names to objects. The namespace that contains a variable determines when that variable is in scope, that is, when it can be used.
Namespaces are how Python keeps track of variables.
In a namespace, an object name maps to the object itself. Namespaces keep track of all objects, not just user-defined variables. For example, functions are also objects in Python. A namespace can contain function names that map to the contents of those functions.
Namespaces are created and destroyed as a program runs. The following code snippet will create a namespace that contains a variable with name x
and value 3. After the program finishes running, this namespace is deleted.
x = 3
Namespaces are often represented with diagrams. The namespace is drawn as a big box containing variable names (words) with arrows pointing to objects (boxes). Inside each box is a value(s). An arrow from a variable name to a box indicates that the name maps to the object that the box represents.
The code snippet above could be represented in a diagram like this:
Namespaces can also be nested. Calling a function creates a new namespace:
def f(x):return xy = f(3)
Here are the namespaces that exist and the objects they contain as this snippet runs. Notice that function parameters are stored in that function’s namespace when it is created (i.e., when the function is called).
Now, let’s return to the LEGB rule. Each letter stands for a different scope.
The local scope is the scope of all objects in the current namespace.
The enclosing scope is the scope of any objects in the namespace that encloses the current namespace, which may or may not exist. In the example below, the namespace of f
encloses the namespace of g
, so the name x
is in the enclosing scope at line 3.
def f(x):def g(y):return yg(x)
The global scope is the scope of objects in the module-level namespace. Creating a variable outside of a function or class will put it in the global namespace.
The built-in scope is the scope of all objects that are built into the Python language. Reserved names like dict
are found in this namespace.
When searching for an object by name, Python first searches the local namespace. If the name is not present in the local namespace, Python searches any enclosing namespaces, starting from the innermost enclosing namespace. Once there are no more enclosing namespaces to search, Python searches the global namespace. If the object name is not defined in any of the preceding namespaces, Python searches the built-in namespace. Finally, if Python cannot find a variable with the requested name in any namespace, it will raise a NameError
exception.
So, Python searches from the inside out. The takeaway is that namespaces are isolated. Creating, deleting, or changing the value of an object in one namespace will not affect objects with the same name in other namespaces.
Let’s look at some examples. Try to predict the output of the following code snippets.
x = 5def f(x):print(f'The value of x is {x}')f(3)
This snippet will define variable x
in the global namespace with value 5
and a function f
that takes one parameter and prints out a string followed by the value of its parameter. When f
is called, its parameter x
is initialized in the function’s local namespace with value 3
. On line 4, Python searches for x
first in the local namespace, finds a name x
that maps to an integer with value 3, and stops its searching there, at the innermost namespace, and prints “The value of x is
”, followed by the value of the x
that it found, “3
.”
dict = 5def f(dict):print(f'The value of x is {dict}')f(3)
This is exactly the same as the previous code snippet except that the name dict
is already defined in the built-in namespace. However, we can redefine variables in outer namespaces, so this snippet will create a variable dict
in the global namespace with value 5
and a variable dict
in the local namespace with value 3
. On line 4, Python again searches from the inside out and first finds the value of dict
inside of the local namespace.
Although this is working code, it is not good practice to redefine built-in variables! It is easy to forget that you have called a variable dict
when you are trying to create a dictionary later in a program. This can lead to confusing errors!
x = 5def f(y):def g(z):print(f'The value of x is {x}')g(y)f(3)
On line 1, we define variable x
in the global namespace with value 5. On line 3, we define function f
in the global namespace. On line 8, we call f
. This creates a local namespace (inside of the global namespace) that contains function g
and parameter y
with value 3. On line 6, we call g
, creating a new local namespace that contains parameter z
with value 3. The previous local namespace, created by calling f
, becomes an enclosing namespace.
Finally, we reach the print statement! Now, Python searches the existing namespaces for an object named x
. There is no such variable in the local namespace, so Python tries the enclosing namespace. There is no x
defined there either, so Python searches the global namespace. There, we find a name x
that maps to an object with value 5. So, Python ouputs “The value of x is
,” followed by the value of the x
in the global namespace, “5
.”
Just one more example! We will only change the first line.
y = 5def f(y):def g(z):print(f'The value of x is {x}')g(y)f(3)
In this snippet, x
is not defined in any namespace. At line 5, Python will search the namespaces in order from local to enclosing to global to built-in, and none of them will have a variable with the name x
. So, Python will raise a NameError
exception.
We can obtain a dictionary of all current global objects by calling the globals()
function. Similarly, we can obtain a dictionary of local objects by calling locals()
.
Suppose you are working inside a function with variable x
defined, but you want to change the value of the variable x
defined at the module level. The LEGB rule tells us that x
will resolve to the object in the local namespace.
x = 5def f():# This new value of `x` will persist only# while the namespace of f still existsx = 3print(f'Before: the value of x is {x}')f()print(f'After: the value of x is {x}')
We can circumvent this by using the keyword global
before the variable name. Declaring global x
will let us use the name x
as if we were in the global namespace.
x = 5def f():# Declaring x as global allows us to modify# its value in the global namespaceglobal xx = 3print(f'Before: the value of x is {x}')f()print(f'After: the value of x is {x}')
To access a variable in the closest enclosing namespace, we can use the keyword nonlocal
.
However, using these keywords is generally considered bad practice. It is confusing to read code that uses objects outside of a modularized portion.
We mentioned that calling functions is one action that creates a new namespace. Are there others?
This is a common confusion for programmers who are used to another language, as rules for creating scopes differ between languages.
if
statementfor
or while
loopThe LEGB rule names the possible scopes of a variable in Python: Local, Enclosing, Global, Built-in. Python searches namespaces in this order to determine the value of an object given its name.
Scopes are created with functions, classes, and modules. If we define objects with the same name in different scopes, they are isolated: at any point, the value of the name will be the value of the object in scope. Similarly, modifying an object by that name will only affect the object in scope.
However, we can explicitly circumvent the scope of a name using the keywords global
and nonlocal
. This is bad practice in most cases, but can occasionally simplify code.
RELATED TAGS
CONTRIBUTOR