Working with variables is fundamental to every programming language, including Python. However, understanding where in a script what variables are accessible is fundamental to being able to work with variables.
The concept of where variables are accessible and where they're not is referred to as variable scoping, and it's what we'll be learning in this chapter.
What is a scope?
We'll start by understanding the meaning of the term 'scope'. In simple words:
A scope can be thought of as an area in a script where given variables are available.
Let's take a quick example. Consider the code below where we define a variable
x at the start of the script:
x = 10 print(x) # 10
x here is accessible everywhere in the script. This area, where
x is defined, is known as the global scope.
Collectively all places in a Python script that are not part of a function, are referred to as the global scope.
A variable defined in the global scope is available everywhere in a Python script — inside functions, inside statement blocks; simply everywhere.
When we talk about the scope of a variable, we refer to all the places where it's accessible.
In the code above, we would say that
x is a globally-scoped variable, or simply that
x is a global variable. This tells us that
x is available in every single location throughout the script.
At the core level, Python has two types of variable scopes:
- The global scope
- The local scope
In the sections below we explore the bits and pieces to both these types, along with considering a handful of examples for each one.
The global scope
Every segment of a Python script, that's not a function's body, is part of the global scope.
A variable defined in the global scope, is formally known as a globally-scoped variable, or simply as a global variable.
As the name might suggest, a global variable is accessible everywhere in a program.
If we look up the meaning of the word 'global', we see that it means relating to the whole of something. In this case, it means exactly the same thing — that a variable is accessible throughout the whole script.
Most of the variable we've been creating so far were all global variables.
Let's see an example once again.
Here we define a variable
x at the start of the script above. This variable is global, and likewise available everywhere throughout the script.
Let's confirm whether
x is really available everywhere.
x = 10 # accessible here print(x) if True: # accessible inside a block print('Inside an if block', x) def print_num(): # accessible inside a function print('Inside a function:', x) print_num()
Inside an if block: 10
Inside a function: 10
As is evident over here, the global variable
x is available everywhere — directly in the script in line 4; inside the body of
if in line 8; inside the function
print_num() in line 12; and so on.
Moving on, although a very trivial detail but still worth discussion, a global variable is available everywhere in a script only after the point it is defined. It's not available before its definition.
Consider the code below:
if True: a = 5 print(a) # 5 print(a) # 5
Here the variable
a, defined inside the
if statement, is indeed global — it's accessible everywhere in the script — but not before its definition. If we try to access it before line 2, we'll get an error.
This is illustrated below:
print(a) if True: a = 5 print(a) print(a)
The error message is pretty much self-explanatory — it says that the name
a is not defined.
Keep this trivial, but useful, fact in mind when working with global variables. Just because it's said that global variables are accessible everywhere doesn't mean that they'll do their magic even before they're actually defined.
The local scope
The area of a function's body defines what's known as a local scope. Specifically, it's referred to as the local scope of the respective function.
A variable defined in a local scope is formally known as a locally-scoped variable, or simply as a local variable.
Contrary to a global variable, a locally-scoped variable is accessible only within the respective function it's defined in.
When we create a variable inside a function in Python, it becomes local to that function. Everywhere inside the function, the variable is accessible, however when we come out of the function, the variable no longer exists.
Consider the following code:
def say_hello(): msg = "Hello programmer!" print(msg) say_hello() print(msg)
msg is defined inside the function
say_hello() and is therefore local to it. The
print() statement in line 3 works fine since
msg is available inside the function.
print() statement outside the function, in line 7, throws an error. This is because
msg is local to
say_hello() — it's not available anywhere outside the function.
All the local variables of a function are automatically deleted when the function completes. This is the reason why local variables aren't accessible after the function within which they're defined completes.
Nested local scopes
We just read above that local variables are accessible everywhere inside their container function.
This means that if we define a variable inside a function
f(), then that variable would be accessible even inside a function
h() defined within the function
Below we demonstrate an example:
def func1(): x = 10 print('Inside func1():', x) def func2(): print('Inside func2():', x) func2() func1()
Inside func2(): 10
x is defined inside
func1() and so it's local to
func1() i.e available everywhere within it. Since
func2() is also part of the function
func1(), the variable
x is available within
func2() as well.
This is confirmed by the
print() statement inside
func2() in line 6. When we call
func2(), it prints out the value of
x without any sort of errors.
One thing to note here is that a variable defined inside
func2() would be local to
func2() only and thereby not accessible in the outer function
This can be seen as follows:
def func1(): def func2(): y = 20 print('Inside func2():', y) func2() print('Inside func1():', y) func1()
We create a variable
func2() and then try to access to it from
As expected, we get an error, since
y is available only within
func2() — outside, in
func1(), no such variable
Variable resolution via LEGB
When the Python interpreter encounters a variable in an expression, it has to resolve it with a given value or throw an error, if the variable doesn't exist. This whole process is known as variable resolution.
The term 'resolution' here comes from the word 'resolve'.
Given that there can be potentially a handful of scopes to look into,
Python follows the LEGB rule to determine what value to resolve a variable with when it's encountered, or whether to throw an error.
This rule simply states the order of scopes in which Python searches for the existence of a given variable.
The LEGB rule is much simpler to understand with the aid of an example.
Consider the following code:
def func1(): def func2(): x = 'local' print(x) func2() func1()
We're concerned with line 4, where we print
x because this statement gets Python to resolve the name
x with a value.
print(x) is encountered in line 4 in the function
func2(), search begins in the local scope of this function for
x. Since a match is found in this scope, the variable
x is resolved with the value of this match i.e
Let's consider another example.
Everything here is the same as in the previous code except for the definition of
x. Previously it was defined inside the function
func2(), but this time it's defined in the outer function
def func1(): x = 'enclosing' def func2(): print(x) func2() func1()
Once again, let's focus on how is
x resolved for the statement
Search begins in the local scope of
func2() based on the LEGB rule. No variable
x is found here, likewise search shifts to the enclosing scope i.e inside
func1(). Here a variable
x is indeed found and so it's used to resolve the name
x in line 5.
Let's see an example of resolution from the global scope:
x = 'global' def func1(): def func2(): print(x) func2() func1()
print() statement is encountered in line 7, and consequently search begings to resolve the name
x. The local scope of
func2() fails to give a match for the name
x, causing the search to shift to the enclosing scope i.e inside
func1(). Even here nothing is found, and so search shifts to the global scope.
It's in the global scope that a variable
x is found, and hence used to resolve the name
x (in line 7).
If the global scope also fails to produce a match for a given name reference, Python finally looks into the built-in list of functions as the last resort to resolution. If the name exists here, it's resolved, otherwise a
NameError is thrown.
Consider the following code:
def func1(): def func2(): print(str) func2() func1()
As before, search begins in the local scope of
func1() as soon as the name
str is encountered in line 3. Nothing as such exists here, likewise search moves one step upwards — into the enclosing scope of
func1(). Once more, nothing is found here causing the search to move another step upwards — out of
func1(), into the global scope.
Even the global scope has no match for the name
str, leaving the interpreter to look finally into the built-in list of names. Here a match for
str is found and ultimately used to resolve the name
str referred to in line 3.
As can be seen from all this long discussion, a variable's resolution occurs in a bottom-to-top approach. We start at the lowest level and then move sequentially upwards until we find a match or reach the end, at which point we throw a
This is Python's usual routine when it needs to resolve a given name with a value, or throw an error if it doesn't exist anywhere. Yes, that's right — this happens for every name that our program refers to!
Python performs the LEGB run in the blink of an eye and so we never notice the grunt work it goes through while resolving each of the names we use in our programs!