DEV Community

Cover image for Deep dive into Python scopes
·ſ
·ſ

Posted on • Updated on • Originally published at blog.julien-maury.dev

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"
Enter fullscreen mode Exit fullscreen mode

You cannot access a outside the local scope of mydef(). The following code throws a scope error :

def mydef() :
    a = "ah"

print(a)
Enter fullscreen mode Exit fullscreen mode

The correct code is :

def mydef() :
    a = "ah"
    print(a)

mydef()
Enter fullscreen mode Exit fullscreen mode

You might think that's relatively easy but look at this :

a = "ah"

def mydef() :
    print(a)

mydef()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 would use such variables typically inside nested functions :

def mydef() :
    a = "ah"

    def mynested() :
        nonlocal a
        a = "ho"

    mynested()

    return a

print(mydef())
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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>}
Enter fullscreen mode Exit fullscreen mode

and for local variables:

{'mynested': <function mydef.<locals>.mynested at 0x108d12048>, 'a': 'ho'}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (2)

Collapse
vlasales profile image
Vlastimil Pospichal

I never use global variables, I am happy.

Collapse
jmau111 profile image
J. Author

It's a good practice. Still it's important to know them.