Modules and Variable Scope

Modules

One of the strengths of Python is that there are many built-in add-ons - or modules - which contain existing functions, classes, and variables which allow you to do complex tasks in only a few lines of code. In addition, there are many other third-party modules (e.g. Numpy, Scipy, Matplotlib) that can be installed, and you can also develop your own modules that include functionalities you commonly use.

The built-in modules are referred to as the Standard Library, and you can find a full list of the available functionality in the Python Documentation.

To use modules in your Python session or script, you need to import them. The following example shows how to import the built-in math module, which contains a number of useful mathematical functions:

In [1]:
import math

You can then access functions and other objects in the module with math.<function>, for example:

In [2]:
math.sin(2.3)
Out[2]:
0.7457052121767203
In [3]:
math.factorial(20)
Out[3]:
2432902008176640000
In [4]:
math.pi
Out[4]:
3.141592653589793

Because these modules exist, it means that if what you want to do is very common, it means it probably already exists, and you won't need to write it (making your code easier to read).

For example, the numpy module, which we will talk about next week, contains useful functions for finding e.g. the mean, median, and standard deviation of a sequence of numbers:

In [5]:
import numpy as np
In [6]:
li = [1,2,7,3,1,3]
np.mean(li)
Out[6]:
2.8333333333333335
In [7]:
np.median(li)
Out[7]:
2.5
In [8]:
np.std(li)
Out[8]:
2.0344259359556172

Notice that in the above case, we used:

import numpy as np

instead of:

import numpy

which shows that we can rename the module so that it's not as long to type in the program.

Finally, it's also possible to simply import the functions needed directly:

In [9]:
from math import sin, cos
sin(3.4)
cos(3.4)
Out[9]:
-0.9667981925794611

You may find examples on the internet that use e.g.

from module import *

but this is not recommended, because it will make it difficult to debug programs, since common debugging tools that rely on just looking at the programs will not know all the functions that are being imported.

Where to find modules and functions

How do you know which modules exist in the first place? The Python documentation contains a list of modules in the Standard Library, but you can also simply search the web. Once you have a module that you think should contain the right kind of function, you can either look at the documentation for that module, or you can use the tab-completion in IPython:

In [2]: math.<TAB>
math.acos       math.degrees    math.fsum       math.pi
math.acosh      math.e          math.gamma      math.pow
math.asin       math.erf        math.hypot      math.radians
math.asinh      math.erfc       math.isinf      math.sin
math.atan       math.exp        math.isnan      math.sinh
math.atan2      math.expm1      math.ldexp      math.sqrt
math.atanh      math.fabs       math.lgamma     math.tan
math.ceil       math.factorial  math.log        math.tanh
math.copysign   math.floor      math.log10      math.trunc
math.cos        math.fmod       math.log1p      
math.cosh       math.frexp      math.modf    

Exercise 1

Does the math.cos funtion take radians or degrees? Are there functions that can convert between radians and degrees? Use these to find the cosine of 60 degrees, and the sine of pi/6 radians.

In [10]:
# enter your solution here

Variable scope

Local scope

In the following example, the variables defined in the function are not available outside the function:

In [11]:
def do_something():
    a = 1
print(a)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-11-ec8025d065ff> in <module>()
      1 def do_something():
      2     a = 1
----> 3 print(a)

NameError: name 'a' is not defined

The variable a is defined in the local scope of the function.

Global scope

Consider the following example:

In [12]:
a = 1

def show_var():
    print(a, b)
  
b = 2
show_var()
1 2

In this case, the function knows about the variables defined outside the function. The variables are in the global scope. This is very useful because it means that modules don't have to be imported inside functions, you can import them at the top level:

In [13]:
import numpy as np

def normalize(x):
    return x / np.mean(x)

This works because modules are objects in the same sense as any other variable. In practice, this does not mean that you should ever use:

In [14]:
a = 1
def show_var():
    print(a)

because it makes the code harder to read. The exception to this are modules, and variables that remain constant during the execution of the program. One exception to this is if you need to define constants (such as pi, or physical constants). See the PEP8 section below for more details.

Local scope has precedence over global scope

Consider the following example:

In [15]:
a = 1

def show_var():
    print(a)
    a = 2
  
show_var()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-15-d7848c1f77b0> in <module>()
      5     a = 2
      6 
----> 7 show_var()

<ipython-input-15-d7848c1f77b0> in show_var()
      2 
      3 def show_var():
----> 4     print(a)
      5     a = 2
      6 

UnboundLocalError: local variable 'a' referenced before assignment

What happened? Variables defined anywhere inside a function are part of the local scope of the function. Any variable in the local scope takes precedence over any other variable, even before it is actually used:

In [16]:
def show_var():
    print(a)
    a = 2

In this case, a is defined inside the function and so it doesn't matter if a is used anywhere else in the Python code. The above function will therefore not work because a is used before it is defined.

Exercise 2

What will the following code print? (think about it, don't run it!):

def double(x):
    x = x * 2
    print(x)

x = 1
double(x)
print(x)

and what about this code?:

def append_3(x):
    x.append(3)
    print(x)

x = [1,2]
append_3(x)
print(x)
In [17]:
# Ok, you can try them out now!

PEP8 style

We just touched on the idea of constants being used in functions - but Python does not really have constants, so how do we recognize these? We now need to speak about coding style.

There is a set of style guidelines referred to as PEP8, which you can find here. These guidelines are not compulsory, but you should follow them as much as possible, especially when you have to work with other people or need other people to read your code.

You don't need to read the guidelines now, but I will first give a couple of examples, then I will show you a tool that can help you follow the guidelines. The following example does not follow the style guidelines:

In [18]:
pi = 3.1415926
def CalculateValues(x):
    return(x*pi)

Constants should be made uppercase, and function names should be lower case separated by underscores (the so called camel-case used above is reserved for classes).

This is the correct way to write the code:

In [19]:
PI = 3.1415926

def calculate_values(x):
    return(x * PI)

Other examples include that indentation should always be 4 spaces, etc. In practice, you can check your code with this script although this does not work in the notebook. Download the script to the folder where you are writing code, and do:

python pep8.py my_script.py

where my_script.py is the script you want to check. For example, you might see:

my_script.py:2:1: W191 indentation contains tabs

The errors include the line number.