Understanding Python scopes

Source: Deep Learning on Medium

Understanding Python scopes

Working as a Deep Learning engineer you find yourself needing to write very different code for different problems. Understanding the nitty gritty details of Python development makes you a much more creative and powerful problem solver.

One of the things we do quite often writing code is using assignments (e.g a = 5). Whether you are calling a function, a class or making a simple calculation, you are probably using scopes. The Scope defines the access you have to different labels created, in different parts of your code. Meaning, calculating a+5 could have a different meaning if it’s being executed from function1 or from function 2. In order to understand exactly what is the “a” you are working with, and making it function as expected, you need to understand scope.

Built-in and Global Scopes

First scope we’ll be talking about is the built in scope. When you’re in the module itself, not inside any function or class, and you write for example:

print('this is just a print')---> this is just a print

Python knows exactly what you mean and it just prints the string you requested. How does Python know what is “print” ? assuming this is our entire script, Python looks for the definition for print in the module, (a.k.a the global scope), and when it does not find any definition for that, it goes up to the built-in scope, where it finds what it needs. Python starts looking at the scope it was executed in and goes up the chain whenever it can’t find what it needs. This means I could easily overwrite “print” and make it behave differently.

def print(*args):
return 3 + 4
print('this is just a print')
---> 7

Now when I execute the function print, I actually just get the integer 7. As there is now definition for print in the global scope, this is what is being used. This global scope definition for print, shadows the built-in scope definition and thus changes its behavior.

Global and Local scopes

The global scope as mentioned is the main module scope, where nothing in particular is defined. If we define a function however, whatever is inside the function is the local scope. This local scope is the context of the function. Lets see how this behaves:

# define x = 40 in the global scope.
x = 'python'
print(x)
---> 'python'
# get access to global scope from a function
def print_global_x():
print(x)
---> 'python'
# define a function to change x
def change_x():
x = 5
print('the local scope x =', x)
change_x()
print('the global scope x =', x)
---> the local scope x = 5
---> the global scope x = 'python'

As we can see, we have two different x. One is 5, the second is ‘python’. You can imagine this gets confusing when we want to use x as an integer, but what we actually get is the string ‘python’. In this case x is 5 in the context of change_x, but is ‘python’ in the context of the main module.

In the function print_global_x we can see again how the process of the looking for the scope functions. It tries the executed scope first, in this case the function itself, it does not find any assignment to x, and so it goes up to the next scope which is the global one where it does find and prints it. If x would not be defined in the global scope as well, Python would look for it in the built-in scope, where it wouldn’t find it, and therefor return a “NameError” exception saying x is not defined.

Controlling the Scope

Does this mean we can only define global scope in the module itself? actually not. We can change the global scope even inside a function, using the global keyword

a = 20def change_a():
global a
a = 4
print('a before change in global scope =', a)
change_a()
print('a after change in global scope =', a)
---> a before change in global scope = 20
---> a after change in global scope = 4

We changed the value a is referencing to (and thus the value we get when we call a) in the global scope, from inside a function. When ever we now try to access a in its global context, whether it’s from a function or from the module, we will get 4.

Now let’s say I have a few nested functions and I need to change the scope of the main function, so it will affect all the others. We can achieve this by using the nonlocal keyword. Here we define a function define_b which defined b and prints it as 4. We then define a new function named change_b that changes the nonlocal context of b to 10. When we later call the new function print_b, we see b has been modified.

If we would try to print b in the global scope, we would still get a NameError not defined as b only exists in the scope of the define_b function.

def define_b():
b = 4
print('b before change =', b)
def change_b():
nonlocal b
b = 10
change_b()

def print_b():
print('new b is ', b)
print('b after change =', b)
print_b()

What scope does nonlocal change ? well, if we would have more nested functions, we would see that nonlocal goes up in the hierarchy to where it can find the next assignment of b. In our case the next time b is assigned, is in the define_b function, therefor it changes this scope. Note that nonlocal would not change the global scope, for this we would still have to use “global b”.

I hope this has made working with scopes somewhat more understandable, as this is a very important topic.

For any additional questions please do not hesitate to contact me at Bakugan@gmail.com or Linkedin