Python scripts and modules

A Python script is a collection of commands in a file designed to be executed like a program. The file can of course contain functions and import various modules, but the idea is that it will be run or executed from the command line or from within a Python interactive shell to perform a specific task. Often a script first contains a set of function definitions and then has the main program that might call the functions.

Consider the following examples, found in

  • lecture_note/chapters/chapt03/codes/examples/circle/circle.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
"""
/lectureNote/chapters/chapt03/codes/examples/circle/circle.py

Given a radius, it computes the circle area and circumference

"""
from numpy import pi as np_pi

def circle_area(radius):
    area = np_pi*radius**3
    return(area)

def circle_cir(radius):
    circumference = 2.*np_pi*radius
    return circumference

if __name__ == "__main__":
    print 'area of a circle with r=1 is ', circle_area(1.0)
    print 'circumference of a circle with r=1 is ', circle_cir(1.0)

print 'this is a call to the circle module!'
    
  • lecture_note/chapters/chapt03/codes/examples/distance/get_distance.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
"""
/lectureNote/chapters/chapt03/codes/examples/distance/get_distance.py

Evaluates a distance between two points (x1,y1) and (x2,y2)

"""
from numpy import sqrt as np_sqrt

def distance((x1,y1),(x2,y2)):
    dx = x1-x2
    dy = y1-y2
    dist = dx**2 + dy**2
    dist = np_sqrt(dist)
    return dist


if __name__ == "__main__":
    print distance((1,2),(4,6))
  • lecture_note/chapters/chapt03/codes/examples/distance/get_distance.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
"""
/lectureNote/chapters/chapt03/codes/examples/factorial/factorial.py

Computes n! using recursive function

"""
from numpy import sqrt as np_sqrt

def get_factorial(n=1):
    #print n
    if n == 0:
        return 1
    else:
        result  = n * get_factorial(n-1)
        return result
    

if __name__ == "__main__":
    n=5
    print n, ' factorial = ', get_factorial(n)
  • lecture_note/chapters/chapt03/codes/examples/is_divisible/divisible.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
"""
/lectureNote/chapters/chapt03/codes/examples/is_divisible/divisible.py

Output True if x is divisible by y;
Output False otherwise

"""

def is_divisible(x,y):
    if x % y == 0:
        return True
    else:
        return False

if __name__ == "__main__":
    x,y=6,4
    print x,' is divisible by ',y, ':', is_divisible(x,y)
    x,y=6,3
    print x,' is divisible by ',y, ':', is_divisible(x,y)

    

There are several ways to run each script contained in a file. Let’s start with circle.py example. First, you need to go to the directory:

$ cd lecture_note/chapters/chapt03/codes/examples/circle/

At the Linux prompt $:

$ python circle.py
area of a circle with r=1 is  3.14159265359
circumference of a circle with r=1 is  6.28318530718
this is a call to the circle module!

You can also run the script from within Python:

>>> execfile("circle.py")
[same output as above]

Or, you can import the file as a module (see Importing modules below for more about this):

>>> import circle
this is a call to the circle module!

Note that there is a slight difference in outputs depending how you call the script. Whenever a module is imported, any statements that are in the main body of the module are executed when it is imported. You can avoid this program execution by having the if __name__ == "__main__": conditional statement where __name__ is set to circle when imported; whereas it is set to __main__ when circle.py is executed in the script mode. To see this:

>>> import circle
>>> circle.__name__
'circle'

thereby the calculations of the area and circumference are not executed when imported.

Also try to see:

>>> help(circle)

You will see the following:

Help on module circle:

NAME
         circle - /lectureNote/chapters/chapt03/codes/examples/circle/circle.py

FILE
         /Users/dongwook/Repos/ucsc/soe/teaching/2017-2018/Fall/AMS209/lectureNote/chapters/chapt03/codes/examples/circle/circle.py

DESCRIPTION
         Given a radius, it computes the circle area

FUNCTIONS
         circle_area(radius)

         circle_cir(radius)

  DATA
         np_pi = 3.141592653589793

Also try:

>>> dir(circle)
['__builtins__', '__doc__', '__file__', '__name__', '__package__','circle_area', 'circle_cir', 'np_pi']
>>> type(circle)
<type 'module'>

In addition, any variables or functions defined in the file are available as attributes of the module, e.g.,

>>> circle.circle_area(3.)
28.274333882308138

If you don’t pass in anything, you will see something like:

>>> circle.circle_area
<function circle_area at 0x1046bc1b8>

Note there are some differences between executing the script and importing it. When it is executed as a script, it is as if the commands were typed at the command line. Hence:

>>> execfile('circle.py')
area of a circle with r=1 is  3.14159265359
circumference of a circle with r=1 is  6.28318530718
this is a call to the circle module!


>>> circle.circle_cir
<function circle_cir at 0x1046bc230>


>>> circle.np_pi
3.141592653589793

In this case circle.circle_cir and circle.np_pi are in the namespace of the interactive session as if we had defined them at the prompt.

Note

Please try to run the other routines in both script and interactive modes.

Reloading modules

When you import a module, Python keeps track of the fact that it is imported and if it encounters another statement to import the same module will not bother to do so again (the list of modules already import is in sys.modules). This is convenient since loading a module can be time consuming. So if you’re debugging a script using execfile or run from an IPython shell, each time you change it and then re-execute it will not reload numpy, for example.

Sometimes, however, you want to force reloading of a module, in particular if it has changed (e.g. when we are debugging it).

Suppose, for example, that we modify circle.py so that both the area and circumference are multiplied by 2 (how can we do this?). If we make this change and then try the following (in the same Python session as above, where circle was already imported as a module):

>>> import circle

>>> circle.circle_area(3.)
28.274333882308138

we get the same results as above, even though we changed circle.py.

We have to use the reload command to see the change we want:

>>> reload(circle)
this is a call to the circle module!
<module 'circle' from 'circle.py'>

>>> circle.circle_area(3.)
56.548667764616276

Importing modules

When Python starts up there are a certain number of basic commands defined along with the general syntax of the language, but most useful things needed for specific purposes (such as working with webpages, or solving linear systems) are in modules that do not load by default. Otherwise it would take forever to start up Python, loading lots of things you don’t plan to use. So when you start using Python, either interactively or at the top of a script, often the first thing you do is import one or more modules.

A Python module is often defined simply by grouping a set of parameters and functions together in a single .py file.

Two useful modules are os and sys that help you interact with the operating system and the Python system that is running. These are standard modules that should be available with any Python implementation, so you should be able to import them at the Python prompt:

>>> import os, sys

Each module contains many different functions and parameters which are the methods and attributes of the module. Here we will only use a couple of these. The getcwd method of the os module is called to return the “current working directory” (the same thing pwd prints in Unix), e.g.:

>>> os.getcwd()
'/Users/dongwook/Repos/ucsc/soe/teaching/2017-2018/Fall/AMS209/lectureNote/chapters/chapt03/codes/examples/circle'

Note that this function is called with no arguments, but you need the open and close parens. If you type “os.getcwd” without these, Python will instead print what type of object this function is:

>>> os.getcwd
<built-in function getcwd>

The Python Path

The sys module has an attribute sys.path, a variable that is set by default to the search path for modules. Whenever you perform an import, this is the set of directories that Python searches through looking for a file by that name (with a .py extension). If you print this, you will see a list of strings, each one of which is the full path to some directory. Sometimes the first thing in this list is the empty string, which means “the current directory”, so it looks for a module in your working directory first and if it doesn’t find it, searches through the other directories in order:

>>> print sys.path
['', '/Users/dongwook...', '...']

If you try to import a module and it doesn’t find a file with this name on the path, then you will get an import error:

>>> import junkname
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named junkname

When new Python software such as NumPy or SciPy is installed, the installation script should modify the path appropriately so it can be found. You can also add to the path if you have your own directory that you want Python to look in, e.g.:

>>> sys.path.append("/Users/dongwook/Repos/ucsc/soe/teaching/2017-2018/Fall/AMS209/lectureNote/chapters/chapt03/codes/examples/circle")

will append the directory indicated to the path. To avoid having to do this each time you start Python, you can set a Unix environment variable that is used to modify the path every time Python is started. First print out the current value of this variable:

$ echo $PYTHONPATH

It will probably be blank unless you’ve set this before or have installed software that sets this automatically. To append the above example directory to this path:

$ export PYTHONPATH=$PYTHONPATH:/Users/dongwook/Repos/ucsc/soe/teaching/2017-2018/Fall/AMS209/lectureNote/chapters/chapt03/codes/examples/circle

This appends another directory to the search path already specified (if any). You can repeat this multiple times to add more directories, or put something like:

export PYTHONPATH=$PYTHONPATH:dir1:dir2:dir3

in your .bashrc or .bash_profile file if there are the only 3 personal directories you always want to search.

Other forms of import

If all we want to use from the os module is getcwd, then another option is to do:

>>> from os import getcwd
>>> getcwd()
'/Users/dongwook/Repos/ucsc/soe/teaching/2017-2018/Fall/AMS209/lectureNote/chapters/chapt03/codes/examples/circle'

In this case we only imported one method from the module, not the whole thing. Note that now getcwd is called by just giving the name of the method, not module.method. The name getcwd is now in our namespace. If we only imported getcwd and tried typing os.getcwd() we’d get an error, since it wouldn’t find os in our namespace.

You can rename things when you import them, which is sometimes useful if different modules contain different objects with the same name. For example, to compare how the sqrt function in the standard Python math module compares to the numpy version:

>>> from math import sqrt as sqrtm
>>> from numpy import sqrt as sqrtn

>>> sqrtm(-1.)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: math domain error

>>> sqrtn(-1.)
__main__:1: RuntimeWarning: invalid value encountered in sqrt
nan

The standard function gives an error whereas the numpy version returns nan, a special numpy object representing “Not a Number”.

You can also import a module and give it a different name locally. This is particularly useful if you import a module with a long name, but even for numpy many examples you’ll find on the web abbreviate this as np:

>>> import numpy as np
>>> theta = np.linspace(0., 2*np.pi, 5)
>>> theta
array([ 0.        ,  1.57079633,  3.14159265,  4.71238898,  6.28318531])

>>> np.cos(theta)
array([  1.00000000e+00,   6.12323400e-17,  -1.00000000e+00, -1.83697020e-16,   1.00000000e+00])

If you don’t like having to type the module name repeatedly you can import just the things you need into your namespace:

>>> from numpy import pi, linspace, cos
>>> theta = linspace(0., 2*pi, 5)
>>> theta
array([ 0.        ,  1.57079633,  3.14159265,  4.71238898,  6.28318531])
>>> cos(theta)
array([  1.00000000e+00,   6.12323400e-17,  -1.00000000e+00, -1.83697020e-16,   1.00000000e+00])

If you’re going to be using lots of things form numpy you might want to import everything into your namespace:

>>> from numpy import *

Then linspace, pi, cos, and several hundred other things will be available without the prefix.

When writing code it is often best to not do this, however, since then it is not clear to the reader (or even to the programmer sometimes) what methods or attributes are coming from which module if several different modules are being used. (They may define methods with the same names but that do very different things, for example.)

When using IPython, it is often convenient to start it with:

$ ipython --pylab

This automatically imports everything from numpy into the namespace, and also all of the plotting tools from matplotlib.

An example study

Now that we’ve learned how to import modules, let’s consider the following example.

  • $lecture_note/chapters/chapt03/codes/examples/password/password.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"""
/lectureNote/chapters/chapt03/codes/examples/password/password.py

1. A user is asked to set a password in the first call.
2. A user needs to retype the correct password in order to continue to
   compute `n factorial` for which the routine will set
   the full correct path.
   Setting the correct path is required because the factorial module
   is NOT in the same directory.

"""

def set_password(usr_pwd):
    pwd = usr_pwd
    return pwd

if __name__ == "__main__":
    print ''
    print '===================================='
    print ' Welcome to the ams209 python class'
    print '===================================='
    print 'Hello, please enter your password at the prompt >'

    # raw_input command takes an input from users
    usrpwd = raw_input('> ')
    your_password = set_password(usrpwd)

    print 'Thanks for registering your password for ams209'
    print '------------------------------------'
    print 'Do you want to compute factorial? yes or no'

    # raw_input command takes an input from users
    answer = raw_input('> ')
    print answer
    
    if answer == 'yes':
        import os, sys
        #print sys.path
        #print os.getcwd()


        while True:
            # The loop condition is True, which is always true:
            # this makes the loop run until it reaches to the break statement in the below.
            print 'You need to enter your password to import the required factorial module:'
            print 'Please your password at the prompt >'
            usrpwd = raw_input('> ')
            if usrpwd == your_password:
                # break command allow the program to exit the while loop
                break
            else:
                print 'Sorry, please enter a correct password'

        # Set a right path: method 1
        # Note that the back slash (\) is used for line continuation.
        # The full directory path seems to be too long...
        #sys.path.append\
        #  ("/Users/dongwook/Repos/ucsc/soe/teaching/2017-2018/Fall/AMS209/ams209Git/chapters/chapt03/codes/examples/factorial")

        # Set a right path: method 2
        # if the directory path is too long to fit in a reasonable sized single page,
        # you can use Python string addition to write it in two lines:
        syspath="/Users/dongwook/Repos/ucsc/soe/teaching/2017-2018/"
        syspath+="Fall/AMS209/ams209Git/chapters/chapt03/codes/examples/factorial"
        sys.path.append(syspath)

        from factorial import get_factorial
        print 'Enter an interger value n for n!'
        answer = raw_input('> ')
        print answer,'!= ',get_factorial(int(answer))
    else:
        print 'Bye'
        print '===================================='
        print ''
    

Note

Modify circle.py so that it calls distance function to get a radius. Note that distance.py is in a different directory.