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)

Answer Yes! I'm just teasing you a little bit ;)

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)

Answer No! This time you get an "UnboundLocalError" with a message "local variable 'count' referenced before assignment".

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)

Answer Yes!

I have another question for you. What is the result of the first print? (click the arrow to discover the answer)

Answer Same as the second print: 111.

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.

Source

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.