Tutorials
python

Scope of Variables in Python Tutorial

In this tutorial, you will learn about Python's scope of variables, the global and nonlocal keywords, closures and the LEGB rule.

If you're familiar with Python, or any other programming language, you'll certainly know that variables need to be defined before they can be used in your program. Depending on how and where it was defined, a variable will have to be accessed in different ways. Some variables are defined globally, others locally. This means that a variable referring to an entity in a certain part of a program, may refer to something different in another part of the program.

In this tutorial, you will learn about:

If you want to learn more about programming in Python, you should definitely take a look at our free Intro to Python for Data Science course. It covers all the basics, from variables and calculations, to lists, functions and packages.

What are variables, really?

To understand the scope of variables, it is important to first learn about what variables really are. Essentially, they're references, or pointers, to an object in memory. When you assign a variable with = to an instance, you're binding (or mapping) the variable to that instance. Multiple variables can be bound to the same instance.

Python keeps track of all these mappings with namespaces. These are containers for mapping names of variables to objects. You can think of them as dictionaries, containing name:object mappings. This allows access to objects by names you choose to assign to them.

In the following example, i is first bound to the integer 5. In this case, i is the variable name, while the integer value 5 is the object.

Then, j is set equal to i. This actually means that j is now bound to the same integer value as i, which is 5.

If you then change i to be equal to 3, an inexperienced programmer might expect j to now also be equal to 3, but that is not the case. j is still bound (or pointing) to the integer value of 5. The only thing that changed is i, which is now bound to the integer value 3.

i = 5
j = i
i = 3

print("i: " + str(i))
print("j: " + str(j))
i: 3
j: 5

A namespace for the code above could look like {i:3, j:5}, and not {i:3, j:i} like you might expect.

The difference between defining a variable inside or outside a Python function

If you define a variable at the top of your script, it will be a global variable. This means that it is accessible from anywhere in your script, including from within a function. Take a look at the following example where a is defined globally.

a = 5

def function():
    print(a)

function()

print(a)
5
5

In the next example, a is defined globally as 5, but it's defined again as 3, within a function. If you print the value of a from within the function, the value that was defined locally will be printed. If you print a outside of the function, its globally defined value will be printed. The a defined in function() is literally sealed off from the outside world. It can only be accessed locally, from within the same function. So the two a's are different, depending on where you access them from.

a = 5

def function():
    a = 3
    print(a)

function()

print(a)
3
5

This is all good and well, but what are the implications of this?

Well, let's say you have an application that remembers a name, which can also be changed with a change_name() function. The name variable is defined globally, and locally within the function. As you can see, the function fails to change the global variable.

name = 'Théo'

def change_name(new_name):
    name = new_name

print(name)    

change_name('Karlijn')

print(name)
Théo
Théo

Luckily, the global keyword can help.

The global keyword

With global, you're telling Python to use the globally defined variable instead of locally defining it. To use it, simply type global, followed by the variable name. In this case, the global variable name can now be changed by change_name().

name = 'Théo'

def change_name(new_name):
    global name
    name = new_name

print(name)    

change_name('Karlijn')

print(name)
Théo
Karlijn

The nonlocal keyword

The nonlocal statement is useful in nested functions. It causes the variable to refer to the previously bound variable in the closest enclosing scope. In other words, it will prevent the variable from trying to bind locally first, and force it to go a level 'higher up'.

Take a look at the three code examples below. In the first one, inner() binds x to "c", outer() binds it to "b" and x is globally defined as "a". Depending on where the variable is accessed from, a different binding will be returned.

x = "a"
def outer():
    x = "b"
    def inner():
        x = "c"
        print("from inner:", x)

    inner()
    print("from outer:", x)

outer()
print("globally:", x)
inner: c
outer: b
global: a

With the nonlocal keyword, you're telling python that the x in the inner() function should actually refer to the x defined in the outer() function, which is one level higher. As you can see from the result, x in both inner() and outer() is defined as "c", because it could be accessed by inner().

x = "a"
def outer():
    x = "b"
    def inner():
        nonlocal x
        x = "c"
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)
inner: c
outer: c
global: a

If you use global, however, the x in inner() will refer to the global variable. That one will be changed, but not the one in outer(), since you're only referring to the global x. You're essentially telling Python to immediately go to the global scope.

x = "a"
def outer():
    x = "b"
    def inner():
        global x
        x = "c"
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)
inner: c
outer: b
global: c

Closures in Python

Closures are function objects that remember values in enclosing scopes, even if they are no longer present in memory.

Remember that a nested function is a function defined in another function, like inner() is defined inside outer() in the example below. Nested functions can access variables of the enclosing scope, but can't modify them, unless you're using nonlocal. In the first example, you'll see that the number = 3 assignment only holds within inner().

def outer(number):
    def inner():
        number = 3
        print("Inner: " + str(number))
    inner()
    print("Outer: " + str(number))

outer(9)
Inner: 3
Outer: 9

If you add nonlocal number in inner(), however, the enclosing scope variable from outer() will be changed by inner(). number = 3 now applies to number in inner() and outer().

def outer(number):
    def inner():
        nonlocal number
        number = 3
        print("Inner: " + str(number))
    inner()
    print("Outer: " + str(number))

outer(9)
Inner: 3
Outer: 3

You may want to preserve a variable defined in a nested function, without having to change a global varibale.

In Python, a function is also considered a object, which means that it can be returned and assigned to a variable. In the next example, you'll see that instead of inner() being called inside outer(), return inner is used. Then, outer() is called with a string argument and assigned to closure. Now, even though the functions inner() and outer() have finished executing, their message is still preserved. By calling closure(), the message can be printed.

def outer(message):
    # enclosing function
    def inner():
        # nested function
        print(message)
    return inner

closure = outer("Hello world!")
closure()
Hello world!

Notice that if you call closure without parentheses, only the type of the object will be returned. You can see that it's of the type function __main__.outer.<locals>.inner.

closure
<function __main__.outer.<locals>.inner>

The LEGB rule

As you saw before, namespaces can exist independently from eachother, and have certain levels of hierarchy, which we refer to as their scope. Depending on where you are in a program, a different namespace will be used. To determine in which order Python should access namespaces, you can use the LEGB rule.

LEGB stands for:

  • Local
  • Enclosed
  • Global
  • Built-in

Let's say you're calling print(x) within inner(), which is a function nested in outer(). Then Python will first look if x was defined locally in that inner(). If not, the variable defined in outer() will be used. This is the enclosing function. If it also wasn't defined there, the Python interpreter will go up another level, to the global scope. Above that you will only find the built-in scope, which contains special variables reserverd for Python itself.

# Global scope

x = 0

def outer():
    # Enclosed scope
    x = 1
    def inner():
        # Local scope
        x = 2

Conclusion

Awesome! You now know what Python's scope of variables is, how you should use the global and nonlocal keywords, and the LEGB rule. You'll be able to easily manipulate variables in nested functions, without any problem. This will be very useful in your future career as a Data Scientist. If you want to advance your Python skills even more, be sure to take a look at our Intro to Python for Data Science course. It's free, and it covers all the basics of the Python programming language.

Want to leave a comment?