Let's Eliminate General Bewilderment • Python's LEGB Rule, Scope, and Namespaces
Let's shed light on what can be a confusing topic
I'm going to be honest with you. I avoided learning about this topic for so long. It's confusing—at first.
There are weird terms like scope and namespace. And then there's the weirder LEGB rule, which reminds me of LEGO blocks.
Did I already lose some of my audience at the sight of those dreadful terms? If you're still here, do stay a bit longer. We'll take this step by step.
The Bit You (Probably) Already Know
Let's create a function and define a variable within it:
I'm calling it an_outer_function()
since I will later introduce another function. I'll let you guess what I'll call that other function!
And you can print out the value of this variable within the function. (Yes, yes. You know this already. I know!)
You need to call the function to execute the code within it.
L
Right, let's move on. But you probably also know the bit coming next:
The print()
call within the function an_outer_function()
displays Otto Fenkton. However, the variable who_am_i
only exists within the function. It doesn't exist outside it. Therefore, the final line with another print()
call, which is in the main program, raises an error:
Otto Fenkton
Traceback (most recent call last):
...
NameError: name 'who_am_i' is not defined
The variable you define within the function is local to the function. That's the L in LEGB, but we'll get to this later.
First, let's focus on one of the other funny words: scope
Scope
A function has its own scope. This means that names defined within the function, such as variable names, only exist within the part of the program that represents the function—this is the function scope. Different scopes allow names to exist only in one part of the program, but not in others.
This is why the variable name who_am_i
doesn't exist in the main program, and you get the NameError
stating that who_am_i
is not defined.
The name is defined locally in the function—it exists in the function's local scope. But it doesn't exist outside the function.
So, let's also create who_am_i
in the main part of the program and print it out. However, you’ll assign a different string to this variable. You can also add more informative text to the printouts:
Two assignments are defining a variable with the name who_am_i
in this code. One of them is the same local variable you saw earlier. This is the one within the function scope. It's local to the function.
What about the other one?
G
The first line in the code defines who_am_i
in the main part of the program. This variable name is in the global scope. This is the part of the program that contains names defined in the main program but doesn't include anything defined within the function.
The two variables named who_am_i
are in different scopes. That's why they can have the same name. That's why one doesn't overwrite the other one. They don't clash. They're in different parts of the program. They're in different scopes.
One is in the function's local scope.
The other is in the program's global scope.
Here's the output from the code above:
I'm hiding in the outer function. I am Otto Fenkton
I'm hiding in plain sight in the main scope. I am Mina Scoope
But let's try adding another global variable name:
You also added two further print()
calls–one in the function and one in the main program.
But hold on a second. The function has its own scope. The new variable and_who_am_i
—the one that starts with 'and'—is not defined in the function. So, what happens when you refer to and_who_am_i
in the function?
I'm hiding in the outer function. I am Otto Fenkton
I'm also in the outer function. I am Manis Cope
I'm hiding in plain sight in the main scope. I am Mina Scoope
I'm also in the main scope. I am Manis Cope
The name Manis Cope, which is in the main scope, is displayed twice.
Why does the print()
call in the function also print Manis Cope rather than return an error? The answer will start shedding some light on the LEGB acronym.
When inside a function, Python first looks for local variables. It finds who_am_i
, the function's local variable (Otto Fenkton). But it doesn't find and_who_am_i
. However, Python doesn't give up. It looks elsewhere. In this case, it looks in the global scope. Here, it does find and_who_am_i
.
When Python doesn't find the local name and_who_am_i
, it looks for a global and_who_am_i
. L comes before G in LEGB, which is why Python looks locally before globally.
Ah, but there's an E in between. Let's deal with this next.
E
Let's add another function to this code. But you'll define this new function within the existing one—and you can remove lines with and_who_am_i
and Manis Cope, too:
The function name an_outer_function
exists in the main program—the global scope. However, names defined within the function are local to it.
The same principle applies to an_inner_function()
. The function name an_inner_function
is a local variable within an_outer_function()
. However, names defined within an_inner_function()
are local to it.
Therefore, the program now has three variables named who_am_i
. But they're defined in different scopes, so they don't clash with each other. One is defined in the main scope, another in the local scope of an_outer_function()
, and the final one in the local scope of an_inner_function()
.
Here's the output from this code:
I'm hiding in the inner function. I am Inna Fenkton
I'm hiding in the outer function. I am Otto Fenkton
I'm hiding in plain sight in the main scope. I am Mina Scoope
Each printout shows a different name.
But what if there wasn't a variable name called who_am_i
in an_inner_function()
? You can comment out the line that defines who_am_i
in an_inner_function()
:
When Python calls print()
within an_inner_function()
, it needs to look for the name who_am_i
. It starts by looking locally within an_inner_function()
, but it doesn't find it.
Where does it look next? Since the acronym is LEGB, it must be the E.
The E stands for the enclosing scope. This is the scope which encloses a nested function, such as an_inner_function()
. In this example, the enclosing scope is the scope of the outer function an_outer_function()
.
Python looks for who_am_i
in the enclosing scope and finds this variable:
I'm hiding in the inner function. I am Otto Fenkton
I'm hiding in the outer function. I am Otto Fenkton
I'm hiding in plain sight in the main scope. I am Mina Scoope
Otto Fenkton, who is defined in the outer function, appears twice now.
And what if Python can't find a name in the local or enclosing scopes?
Then it moves on to the G in LEGB—the global scope:
I'm hiding in the inner function. I am Mina Scoope
I'm hiding in the outer function. I am Mina Scoope
I'm hiding in plain sight in the main scope. I am Mina Scoope
Mina Scoope, who exists in the main scope, appears three times now. There's only one variable named who_am_i
, and it's the one in the global scope.
Let's remove the comments to bring back the other two assignments to who_am_i
:
And all three names reappear in the output:
I'm hiding in the inner function. I am Inna Fenkton
I'm hiding in the outer function. I am Otto Fenkton
I'm hiding in plain sight in the main scope. I am Mina Scoope
Enclosing scopes exist only when you have nested functions. Another name for an enclosing scope is a nonlocal scope, and Python has a nonlocal
keyword, which enables you to define a variable in the enclosing or nonlocal scope of a nested function:
One of the three names will appear twice in this code's output. Which one?
Don't peek. Think.
...
Here's the answer:
I'm hiding in the inner function. I am Inna Fenkton
I'm hiding in the outer function. I am Inna Fenkton
I'm hiding in plain sight in the main scope. I am Mina Scoope
Since you declared who_am_i
within an_inner_function()
to be nonlocal, it's defined in the enclosing or nonlocal scope. This is the scope of the enclosing function an_outer_function()
. Therefore, when you assign Inna Fenkton to who_am_i
, this assignment overrides the previous assignment of Otto Fenkton since this is the same variable that's in the scope of an_outer_function()
.
Confused? You may want to read the previous few paragraphs again.
There's also a global
keyword that performs a similar action but places the variable in the global scope. However, the global
keyword is evil in (almost) all scenarios. Don't use it. And I won't demonstrate it in this article, either.
B
The acronym LEGB also has a B at the end. But what's left? Let's summarise the first three letters first.
The global scope always exists when you run a Python program.
The local scope exists whenever you define a function. And each function has its own local scope.
The enclosing (or nonlocal) scope exists whenever you have a nested function. The inner function is enclosed in an outer function. The outer function's scope is the enclosing scope.
What if Python doesn't find a name in any of these three scopes? "How's that possible?" you may be thinking.
Look at the code above again. And look at the print()
call within an_inner_function()
.
There's print
. This is also a name in Python. The program needs to find what the name print
refers to.
It looks Locally. There's nothing.
It looks in the Enclosing scope. No luck.
It looks in the Global scope. Still no print
to be found.
Therefore, it looks in the Built-in scope. This scope contains all the names that are present the moment you run a Python program. And, as I'm sure you're aware, print
is there.
Python looks in the built-in scope last. This is why you need to be careful not to redefine a built-in name:
You define the name print
as a string in the first line of this REPL session. Therefore, you create the name print
in the program's global scope.
On the second line, Python looks for the name print
. Since G comes before B in LEGB, Python looks in the global scope before the built-in scope. Therefore, print
is no longer a name that refers to the much-loved function. Instead, it now refers to the string "Don't do this at home!"
. The code raises a TypeError
when you add parentheses after print
since you can't call a string!
Namespace
The concepts of scope and namespace are related but distinct. A namespace in Python is a dictionary that maps names to the objects they represent.
You can display the local namespaces for functions using the locals()
function. Some lines are commented out in the version below:
This code shows the local namespaces within an_inner_function()
and an_outer_function()
:
Local namespace in 'an_inner_function'
{'who_am_i': 'Inna Fenkton'}
Local namespace in 'an_outer_function'
{
'who_am_i': 'Otto Fenkton',
'an_inner_function': <function an_outer_function.<locals>.an_inner_function at 0x1060f7490>
}
And when you import a module, such as when you use import random
, the module also has its own namespace. This is the reason why you need to access its contents using the module name followed by a dot.
However, if you use the from
keyword to import names, such as from random import randint
, the name you import is imported directly into the global namespace.
But I won't dwell too much on the distinction between scope and namespace here…
Beware: Not All Indented Blocks Have Their Own Scope or Namespace
You define a function. It has its own local namespace.
You define a class, which also has its own namespace.
However, not all indented blocks of code create a new scope. Many other Python indented blocks don't have their own scope, such as for
and while
loops, if
blocks, and more.
Here's proof:
The assignment within the for
loop's indented block, which is repeated ten times in this example, overwrites the assignment on the first line:
I'm hiding in plain sight in the main scope. I am Mina Scoope
I'm hiding in plain sight in the main scope. I am Flora Loopla
There's no local scope in a for
loop. The code in the for
block is part of the global scope.
Functions and classes create re-usable code, which you can call several times in different places in your program, which is why they need their own scope.
However, other indented blocks, such as if
statements and for
loops, say, are executed wherever they appear in the code. Therefore, they're more suited to share the scope they're in rather than have their own scope.
Incidentally, and apologies for going off on a tangent, but this is still related to this article's topic. Compare these two programs, which perform similar tasks using different techniques. Here's the first one:
Notice that the for
loop uses the variable name who_am_i
within the for
loop statement. This is the same variable defined on the first line, as they're both in the global scope. The output shows the value of this variable at the end of the program:
Forlan Alste
This is the last value assigned to who_am_i
in the final iteration of the for
loop. The code in the for
loop overwrites the contents of who_am_i
.
Now, here's the second version, which uses a list comprehension:
The list names
has the same values in both versions. The list comprehension uses who_am_i
as its variable name. But look at the value of who_am_i
at the end of the program:
Mina Scoope
A list comprehension has its own scope, unlike a classic for
loop. Therefore, the list comprehension does not overwrite the contents of who_am_i
in the global scope.
Final Words
I'll end this article by reminding you of Monty and the White Room. If you don't know what I'm talking about, read this three-part series about this analogy, which is my favourite in programming.
In this analogy, Monty is the personification of a computer program. He works in a room which provides the infrastructure for the program. There are modules and variables in this room. This is the global namespace.
When you define a function in your program, Monty builds a room adjacent to the main room. The door leading from the main room to the function room has a label with the function name. The function name is in the scope of the main room. But anything happening in the function is in another room—a different scope.
When Monty is in a function room, he first looks in this room for any name he needs. If he doesn't find the name, he goes back to the main room—the global scope.
I don't quite deal with enclosing scope in that analogy, but you can extend it to deal with nonlocal scope easily.
And if Monty doesn't find anything in the function room, and doesn't find anything in the main room, he'll then pick the built-in booklet, which has all the names in the built-in scope.
Therefore, if you're a fan of Monty and the White Room analogy, you already understand scope and the LEGB rule!
Code in this article uses Python 3.13
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
And you can find out more about me at stephengruppetta.com
Further reading related to this article’s topic:
For more on the need for local variables, read this narrative-style essay: The Mayor of Py Town's Local Experiment: A Global Disaster
Related to this topic. Names on Python: What's In A Name?
I referred to this analogy in the text. It's also a chapter in The Python Coding Book. And you can find a version of it here Monty and The White Room Analogy. And these are the three separate parts of this series:
Do you want to see an example where inner functions are used? Read this: The Magician's Sleight of Hand
And more on scope and the LEGB rule at Real Python: Python Scope & the LEGB Rule: Resolving Names in Your Code – Real Python
And a bit more on inner functions: Python Inner Functions: What Are They Good For? – Real Python
Appendix: Code Blocks
Code Block #1
def an_outer_function():
who_am_i = "Otto Fenkton"
Code Block #2
def an_outer_function():
who_am_i = "Otto Fenkton"
print(who_am_i)
an_outer_function()
Code Block #3
def an_outer_function():
who_am_i = "Otto Fenkton"
print(who_am_i)
an_outer_function()
print(who_am_i)
Code Block #4
who_am_i = "Mina Scoope"
def an_outer_function():
who_am_i = "Otto Fenkton"
print(f"I'm hiding in the outer function. I am {who_am_i}")
an_outer_function()
print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
Code Block #5
who_am_i = "Mina Scoope"
and_who_am_i = "Manis Cope"
def an_outer_function():
who_am_i = "Otto Fenkton"
print(f"I'm hiding in the outer function. I am {who_am_i}")
print(f"I'm also in the outer function. I am {and_who_am_i}")
an_outer_function()
print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
print(f"I'm also in the main scope. I am {and_who_am_i}")
Code Block #6
who_am_i = "Mina Scoope"
def an_outer_function():
who_am_i = "Otto Fenkton"
def an_inner_function():
who_am_i = "Inna Fenkton"
print(f"I'm hiding in the inner function. I am {who_am_i}")
an_inner_function()
print(f"I'm hiding in the outer function. I am {who_am_i}")
an_outer_function()
print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
Code Block #7
who_am_i = "Mina Scoope"
def an_outer_function():
who_am_i = "Otto Fenkton"
def an_inner_function():
# who_am_i = "Inna Fenkton"
print(f"I'm hiding in the inner function. I am {who_am_i}")
an_inner_function()
print(f"I'm hiding in the outer function. I am {who_am_i}")
an_outer_function()
print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
Code Block #8
who_am_i = "Mina Scoope"
def an_outer_function():
# who_am_i = "Otto Fenkton"
def an_inner_function():
# who_am_i = "Inna Fenkton"
print(f"I'm hiding in the inner function. I am {who_am_i}")
an_inner_function()
print(f"I'm hiding in the outer function. I am {who_am_i}")
an_outer_function()
print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
Code Block #9
who_am_i = "Mina Scoope"
def an_outer_function():
who_am_i = "Otto Fenkton"
def an_inner_function():
who_am_i = "Inna Fenkton"
print(f"I'm hiding in the inner function. I am {who_am_i}")
an_inner_function()
print(f"I'm hiding in the outer function. I am {who_am_i}")
an_outer_function()
print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
Code Block #10
who_am_i = "Mina Scoope"
def an_outer_function():
who_am_i = "Otto Fenkton"
def an_inner_function():
nonlocal who_am_i
who_am_i = "Inna Fenkton"
print(f"I'm hiding in the inner function. I am {who_am_i}")
an_inner_function()
print(f"I'm hiding in the outer function. I am {who_am_i}")
an_outer_function()
print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
Code Block #11
print = "Don't do this at home!"
print("Hello")
# Traceback (most recent call last):
# ...
# TypeError: 'str' object is not callable
print
# "Don't do this at home!"
Code Block #12
who_am_i = "Mina Scoope"
def an_outer_function():
who_am_i = "Otto Fenkton"
def an_inner_function():
# nonlocal who_am_i
who_am_i = "Inna Fenkton"
# print(f"I'm hiding in the inner function. I am {who_am_i}")
print(f"Local namespace in 'an_inner_function'\n{locals()}")
an_inner_function()
# print(f"I'm hiding in the outer function. I am {who_am_i}")
print(f"Local namespace in 'an_outer_function'\n{locals()}")
an_outer_function()
# print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
Code Block #13
who_am_i = "Mina Scoope"
print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
for something in range(10):
who_am_i = "Flora Loopla"
print(f"I'm hiding in plain sight in the main scope. I am {who_am_i}")
Code Block #14
who_am_i = "Mina Scoope"
names = []
for who_am_i in ["Flora Liste", "Forlan Alste"]:
names.append(who_am_i)
print(who_am_i)
Code Block #15
who_am_i = "Mina Scoope"
names = [who_am_i for who_am_i in ["Flora Liste", "Forlan Alste"]]
print(who_am_i)
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
And you can find out more about me at stephengruppetta.com
I'm not gonna ask you to demonstrate the `global` keyword but do you think you could elaborate on why I shouldn't be used beyond just "it's evil"? Why is it bad?
So pleased to see this article - I have got myself in a right mess with scopes etc in the past - and look forward to working through this