# Modules

## Episode 6 - Modules

Modules are very important in Python programming. In fact, every script you write is a module, whether you use it in such a manner or not. Modules enable manageable chunks of related code to be encapsulated. They make it easier and more effective for multiple programmers to work on a project. And finally, they encode certain principles of good software design, such as code reuse.

We will not discuss classes in this introductory tutorial series, but classes in Python are normally implemented within their own module, or at most a few closely-related classes are in a module. So it is important to understand modules for good Python programming practices. Furthermore, packages are bundles of modules, and there are hundreds, if not thousands, of packages available for you to use for all manner of programming tasks. Understanding how modules work is a requirement to be able to utilize these packages.

In order to access the functions in a module, we must `import`

the module. Doing so defines a *namespace*. A namespace is an identifier for all the elements defined in the module. The default namespace of a module is the name of its file, without the `.py`

suffix. For packages, the namespace will be controlled by the master module. So for example

```
import math
x = 257.3
z=math.sqrt(x) #sqrt is a function within the math namespace
print(z)
import os
home_dir=os.getenv("HOME") #or "HOMEPATH" for Windows
print(home_dir)
import numpy
A=numpy.zeros(200)
A[75:100] = 1.0
print(A[88])
```

In this example, we import the `math`

module using its default namespace. We then must prefix all the members of this namespace with `math`

followed by a period. We also import the `os`

module and use a function from it. Finally we import the NumPy master module (note that its native namespace is all lower case) and set up an array.

If we wish to change the namespace identifier, perhaps to avoid a lot of typing, we can change it when we import, but not thereafter.

```
import numpy as np
B=np.zeros(200)
```

We can import specified symbols, and only those symbols, with another form of the import statement.

```
from math import sqrt, exp
w=sqrt(19.)
y=exp(-2.)
```

When we use this form of import, we do not use a prefix with the items we imported.

```
w=sqrt(19.)
y=exp(-2.)
```

Finally, if we wish to import all the symbols in the module without requiring a prefix, use the form

```
from math import *
```

This should be avoided in most circumstances, because of the possibility of *namespace
collisions* when two modules might contain functions with the same names. For example, both the math module and the NumPy module contain functions called `sin`

, but the NumPy version has capabilities the math version does not have. We would generate an error if we tried to use the math version with a NumPy array argument. If it has no other way to distinguish a function name, the interpreter will use the one it saw last. It is frequently acceptable to import * with math, however. We could then use sin as the math version and e.g. `np.sin`

as the NumPy version.

The namespace of the main module for your program is a special string `__main__`

(two underscores on each side of the word main). The system assigns this namespace to whatever file is run directly through the interpreter. When you write a Python program, it is good practice to place all the general work that is not in other functions into a function conventionally called `main()`

. We then invoke it with

```
if __name__=="__main__":
main()
```

For example:

```
def main():
print("Starting the program")
# more code
print( "Program ended")
if __name__=="__main__":
main()
```

Recall that we said any file you write is a module. If you include the

```
if __name__=="__main__":
main()
```

clause, then anything in `main()`

will be ignored if you import that file into another module.

Let’s practice this on an example. We are going to implement one of the oldest numerical algorithms known, the “Babylonian method” for finding square roots. This method relies on the fact that if S is an overestimate of the square root of a nonnegative number x, then x/S is an underestimate, so their average is a better estimate than S was. Expressed as an algorithm, we start with a guess for sqrt(x) that we know is too large – we could use the number itself to get started—we then compute x/S and average it with S. Initially this gives us (x+1.)/2. as our next guess. In code, this looks like an update

```
S=0.5*(S+x/S)
```

We continue to update S until we reach the desired accuracy.

Our function will perform the following steps:

- Initialize S
_{0} - Loop while the difference between S
_{n}and S_{n+1}is greater than the specified tolerance - Compute S
_{n+1}from S_{n}

In code this becomes

```
def bab_sqrt(x):
"""This function implements the Babylonian method to find the
square root """
tol=1.e-14
old_sqrt=x
new_sqrt=(x+1)/2.
while abs(new_sqrt-old_sqrt)>tol:
old_sqrt=new_sqrt
new_sqrt=0.5*(old_sqrt+x/old_sqrt)
return new_sqrt
```

Call the file with this function `test_bab_sqrt.py`

. Now let’s write a `main()`

function

```
def main():
tests=[0.,4.,9., 16., 25.]
for x in tests:
my_sqrt=bab_sqrt(x)
print(x," ",my_sqrt)
if __name__=="__main__":
main()
```

Run the code. All should seem fine. Now let’s change tests to

```
tests=[2., 10.3, -2., 0.45]
```

We have a problem when we reach `-2`

. You’ll need to click the red button at the top of the iPython console to stop the runaway program. Our mistake was to try to use an algorithm that works only for nonnegative numbers on a negative value; in this case it will never converge.

We go back to our function to add code to make it check whether its input value is nonnegative. Make the first line

```
if (x<0): return None
```

If this tests `True`

it will immediately return.

Now cut your `bab_sqrt`

function and paste it into a file `b_sqrt.py`

. At the end our `b_sqrt.py`

file will contain

```
def bab_sqrt(x):
"""This function implements the Babylonian method to find the
square root """
if x<0: return None
tol=1.e-14
old_sqrt=x
new_sqrt=(x+1)/2.
while abs(new_sqrt-old_sqrt)>tol:
old_sqrt=new_sqrt
new_sqrt=0.5*(old_sqrt+x/old_sqrt)
return new_sqrt
```

and our `test_bab_sqrt.py`

will contain

```
def main():
tests=[2., 10.3, -2., 0.45]
for x in tests:
my_sqrt=bab_sqrt(x)
print("x:", x, "sqrt:", my_sqrt)
if __name__=="__main__":
main()
```

In order to use `bab_sqrt`

in `test_bab_sqrt.py`

we will need to add an import statement.

Place this line below the import from math:

```
from b_sqrt import bab_sqrt
```

Let’s do a more informative comparison of our result. We’ll import `sqrt`

from the math module and print the difference between our computation and the standard result. Now we can use the `is`

operator to test in our main function (this is a slightly more “Pythonic” way to test than using the double equals as we did in our video):

```
from math import sqrt
from b_sqrt import bab_sqrt
def main():
tests=[0.,-4.,9., 16., 25.]
for x in tests:
my_sqrt=bab_sqrt(x)
if my_sqrt is None:
my_sqrt = str(my_sqrt)+"\t"
real_sqrt = sqrt(x)
difference = real_sqrt
else:
real_sqrt=sqrt(x)
difference = abs(real_sqrt-my_sqrt)
print("x:", x, " bab_sqrt:", my_sqrt, \
"\tsqrt:", real_sqrt, "\tdiff: ", difference)
if __name__=="__main__":
main()
```

You have now written your own modules and imported one into another. Next episode we will discuss several important packages you can use in your programs.

Modules are important concepts in Python. Some good sources include

https://www.tutorialspoint.com/python/python_modules.htm

https://docs.python.org/2/tutorial/modules.html

## VIEW EXERCISE SOLUTION (`Slots.py`)

```
"""
Slots simulator
"""
import random
def create_reel():
reel=['$']
reel.extend(['7']*2)
reel.extend(['cherry']*3)
reel.extend(['diamond']*4)
reel.extend(['melon']*4)
reel.extend(['orange']*4)
reel.extend(['grape']*4)
reel.extend(['bar']*5)
reel.extend(['lemon']*5)
random.shuffle(reel)
return reel
def spin(reel):
return random.choice(reel)
def pull(reel):
display=[spin(reel),spin(reel),spin(reel)]
return display
def table(element):
if element=='$':
return 100.
elif element=='7':
return 75.
elif element=='cherry':
return 50.
#a reward for any match
elif element=='bar' or element=='lemon':
return 10.
else:
return 25.
def main():
reel=create_reel()
print("Welcome to Python Slots!")
play=raw_input("Are you ready to play? y/n:")
if play=='y':
won=0
lost=0
while True:
bet=float(raw_input("Place your bet:"))
display=pull(reel)
print(display)
if display[0]==display[1] and display[1]==display[2]:
print("You win!!!")
payout=bet*table(display[0])
won+=payout
else:
print("You lose!")
lost-=bet
print("Won:",won," Lost:",lost)
play=raw_input("Do you want to keep playing? y/n:")
if play=='n':
break
else:
continue
if __name__=="__main__":
main()
```