Demystifying Python Scopes (with Examples)

Roy Ben Yosef
CyberArk Engineering
4 min readMay 17, 2023
Photo by Artturi Jalli on Unsplash

If you’re coming from a programming language other than Python, such as C++, Java or C#, you might not be aware of the differences in Python’s scoping. By scoping, I mean the visibility of a name (e.g., a variable or more generally, an object).

Note: This isn’t supposed to be a full guide to Python scopes. This would have required a much lengthier post. Instead my goal is to help you get a foothold on this topic, and a good starting point from which you can keep learning.

Other Languages

In languages such as C, C++, Java, C#, each variable is defined in its enclosing block — that is, the curly braces of a function, if, loop, try block, etc.

Let’s look at some examples:

In Java, this is an error, since n is already defined in the outer scope (the “main” function):

class HelloWorld {
public static void main(String[] args) {
int n = 1;
System.out.println(n);
if (true) {
int n = 2;
System.out.println(n);
}
}
}

// HelloWorld.java:10: error:
// variable n is already defined in method main(String[])
// int n = 2;

And the following is an error, since n is only defined in the inner scope (the “if” block) and is not visible outside of it:

class HelloWorld {
public static void main(String[] args) {
if (true) {
int n = 2;
System.out.println(n);
}
System.out.println(n);
}
}

// HelloWorld.java:10: error: cannot find symbol
// System.out.println(n);

Scoping in Python

If you’re new to Python you might be surprised to learn that this code is valid:

if condition:
n = 1
else:
n = 2

print(n)

Let’s understand why:

In Python, a name is visible, or defined at the block level. Let’s break this down:

A name is generally an object (in our examples the variables), and the full definition can be found under “Binding of names” in the Python docs.

Here is the definition of a “block” according to the the “Execution Model” section in the python docs:

The following are blocks: a module, a function body, and a class definition.

Notice how if, loops, try clauses and others are NOT part of this definition.

As always, things are never this simple, but this is the basic understanding you should have: The “block level” for us is either a function, a class or a module (a python file in simple terms).

An important thing to note is that if one of your code branches do not define your variable, you might be in trouble, so you might want to define your variable ahead of time anyway:

flag = False
if flag:
n = 1
else:
print('I did not define n here')

print(n)

# Exception has occurred: NameError
# name 'n' is not defined

All the fine details can be found on the Python docs page “Execution Model” which I recommend reading.

Leg up with LEGB

The last thing I want to mention is how Python resolves names in your code (For example, if you have the same name in different places in your code), which is generally referred to as the “LEGB rule”.

In simple terms it means that Python will look for names (and stops when it finds one) in this order:

  1. Local names — e.g., a local variable defined in a function.
  2. Enclosed names — e.g., a variable in a nested function — a function within another function.
  3. Global names — e.g., a globally defined variable.
  4. Built-in Python names: e.g., True, False, int, def and so on.

In the following example, you can see how my_var (2) in foo takes precedence over the global variable, such that it defines a new variable under foo and does not set the value of the global my_var (1)

my_var = 1 # (1) Global var

def foo():
my_var = 2 # (2) Local var

def bar():
my_var = 3 # (3) Enclosed var

According to the LEGB rule, this means that foo’s my_var (2), is a different variable than the global one.

If you wanted to set the global variable, you could use the ‘global’ keyword:

my_var = 1 # Global var

def foo():
global my_var
my_var = 2 # Sets the global var

Similarly, you can set the function variable (foo’s my_var2) from the nested function this way:

def foo():
my_var2 = 2

def bar():
nonlocal my_var2
my_var2 = 3 # Sets the function's my_var2

bar()

Remember, this post is really just the tip of the iceberg, and to understand all the details I recommend reading this beautifully detailed post in “Real Python”, about the LEGB rule.

Understanding Python Scoping

When I was getting started with Python (moving from C#, C++ and Java), I was confused by how scoping works. I hope that this helps you understand the basics of Python scoping and makes you want to dive deeper and learn all about it.

--

--

Roy Ben Yosef
CyberArk Engineering

Sr. Software architect at CyberArk’s Technology Office. Into code, architecture and problem solving. Like to build and fix stuff. Usually late at night.