Deep dive into Python scopes

Python has its way to handle scopes. It’s essential to understand it to get to the next level.
Local vs. global
Only global variables are available anywhere. Other variables belong to a specific area of the program.
For example, any variable created inside a function is a local variable:
def mydef() :
a = "ah"
You cannot access a
outside the local scope of mydef()
. The following code throws a scope error :
def mydef() :
a = "ah"
print(a)
The correct code is :
def mydef() :
a = "ah"
print(a)
mydef()
You might think that’s relatively easy but look at this :
a = "ah"
def mydef() :
print(a)
mydef()
Is it still working? (click the arrow to discover the answer)
Here comes the fun part, look at the following code now:
count = 0
def mycount() :
count = count + 1
print(count)
mycount()
Is it working? (click the arrow to discover the answer)
We’ll cover that case in the next section.
Understanding the scope error
What the heck is this “UnboundLocalError” we got?
Sure there’s an error, but which one? We call the following an assignment :
count = count + 1
Because we did that assignment inside a function, the compiler considers now count
as a local variable, not a global variable anymore. That’s why you get an error “local variable ‘count’ referenced before assignment”. The compiler reads count
before it’s assigned.
In Python 3, you solve that problem by explicitly saying count
is a global variable :
count = 0
def mycount() :
global count
count = count + 1
print(count)
mycount()
A little more about globals
In the previous section, we saw an example of a scope error. Now, let’s have a look at the following :
count = 1
def mycount() :
print(count)
count += 110
mycount()
print(count)
Is it working? (click the arrow to discover the answer)
I have another question for you. What is the result of the first print? (click the arrow to discover the answer)
Whaaaat….?
Yes, Python implicitly considers count
as a global variable unless you make an assignment inside your function.
It might seem weird at first, but this way, Python wants to prevent potential unwanted side-effects.
What is nonlocal
?
Python 3 introduced the nonlocal
statement for variables that are neither local nor global.
Again, what…?
You’d use such variables typically inside nested functions :
def mydef() :
a = "ah"
def mynested() :
nonlocal a
a = "ho"
mynested()
return a
print(mydef())
In this case, neither a global nor a local statement can resolve the naming conflict, but the nonlocal
statement can.
The LEGB Rule
Another common way of explaining scopes is to say it’s the context in which variables exist.
The Python interpreter reads variables in a specific order :
Local -> Enclosing -> Global -> Built-in
Each scope is nested inside another scope. The final scope is the Built-In scope. Python raises a NameError if it does not found your variable after exploring each level in that specific order.
Bonus
You can use the built-in functions globals()
and locals()
to “see the Matrix” :
def mydef() :
a = "ah"
def mynested() :
nonlocal a
a = "ho"
mynested()
print(locals())
return a
mydef()
print(globals())
Which displays for global variables:
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10c1f7550>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'scopes.py', '__cached__': None, 'mydef': <function mydef at 0x10c1ac1e0>}
and for local variables:
{'mynested': <function mydef.<locals>.mynested at 0x108d12048>, 'a': 'ho'}
Wrap up
I hope you enjoyed this post. Python has four scopes. It reads your variables according to the LEGB rule. It handles local and global variables in a pretty unique way compared to other languages such as Java, or even C.
IMHO, it’s a good design.