Week 13 - Sys and Modules

Today's Agenda

  • Sys
  • Writing your own module
  • Adding your module to you Python Path

Sys

The sys module provides a lot of information about the Python interpreter. The functionality that we're interested in is the ability to use sys to parse information from the command line. This can be used to make programs and modules useful when run from the command line.

After importing sys, sys.argv provides a list of all the parameters that the current python script was called with. A program that doesn't take any paramters will only have one item in this list, where the 0th item is the name of the program. Any paramters that are passed into the script will be listed after.

From a command line, try running the following in the notebooks directory:

python arguments.py arg1 arg2

In this script from http://www.python-course.eu/sys_module.php, we see that the 0th term in sys.argv is arguments.py and the rest of the list is the parameters we passed in. This will come in handy with our module.


In [ ]:
# %load ./arguments.py
#!/usr/bin/python

import sys

# it's easy to print this list of course:
print sys.argv

# or it can be iterated via a for loop:

for i in range(len(sys.argv)):
    if i == 0:
        print "Function name: %s" % sys.argv[0]
    else:
        print "%d. argument: %s" % (i,sys.argv[i])


# copied from http://www.python-course.eu/sys_module.php

Writing a Module

There will always be some code that you have to write yourself. When you're working on a single script that does the same things repeatedly, it makes sense to start writing functions within the script. However, you may also have operations that are carried out in multiple scripts for various projects. In this case, we'd like to be able to use that same function in all scripts. This will be very similar to writing a single function, so we'll try out the function, but the file that contains our module is stellar_radius.py

For this function, we'll say that we want to be able to take the mass of a star and calculate the radius. For smaller-mass stars, we can approximate this as $R=M^{0.8}$


In [2]:
def stellar_r(mass):
   starR=mass**0.8
   return starR

This is a really simple equation, but by making this a function, we can make it more sophisticated or change the exponent in one spot, and it will be updated everywhere. We can quickly test this out to see that the function works the way that we expect when called.


In [3]:
print(stellar_r(1))
print(stellar_r(0.9))


1.0
0.91916611884

You can also pass a NumPy array to the function


In [4]:
%matplotlib inline
import matplotlib.pyplot
import numpy as np

In [5]:
mass_arr = np.arange(0.8, 5, 0.1)
stellar_r( mass_arr )


Out[5]:
array([ 0.83651164,  0.91916612,  1.        ,  1.07923035,  1.157031  ,
        1.2335441 ,  1.30888783,  1.38316187,  1.45645136,  1.52882979,
        1.60036117,  1.6711017 ,  1.74110113,  1.81040373,  1.87904917,
        1.94707315,  2.01450799,  2.08138302,  2.14772503,  2.21355854,
        2.27890607,  2.34378839,  2.40822469,  2.47223275,  2.53582911,
        2.59902916,  2.66184727,  2.7242969 ,  2.78639063,  2.8481403 ,
        2.90955705,  2.97065136,  3.03143313,  3.09191172,  3.15209597,
        3.21199429,  3.27161463,  3.33096455,  3.39005126,  3.44888161,
        3.50746212,  3.56579904])

If we want to use this module that we've created, we can inport it as we have with other modules. When we import it, we're using the file name, and then when we call the function, we have to include the function name and for this module those two are the same. These could also be different, where the file name is one thing, and then it contains one or more functions that have names that don't match it.


In [6]:
import stellar_radius as staR
print staR.stellar_radius(0.9)


0.91916611884

This is what the "stellar_radius.py" file looks like:


In [ ]:
# %load ./stellar_radius.py
#!/usr/bin/env python
import numpy as np
import sys

def stellar_radius(mass):
   starR=mass**0.8
   return starR

if __name__ == "__main__":
   print stellar_radius(float(sys.argv[1])) #mass

This all works for if we simply want to import the function into another python function, but it can often be useful to be able to run these functions from a command line (for example, so that we can use execute this python function in another scripting language or so that we can run it from the command line for quick calculations)

The simplest form of a module file just contains a series of functions without running any of them, but we can add some additional code to the module file that will be run if this file is executed rather than imported.

This code consists of an if statement. The __name__ property relates to what the code is called, and if the file is called directly, it will be True. If the code is imported, then this code won't be executed. What follows is a single line that calls the stellar radius function using the argument that will get passed in from the command line.

if __name__ == "__main__":  
       print stellar_radius(float(sys.argv[1])) #mass

We can test this out, then, by going into the directory for the notebook and calling the code from the terminal python stellar_radius.py 0.9

We could also call this code from another operating language if needed.

Adding your modules to your Python Path

Normally, you want to have a unique folder, in which you save all of your modules and you can reuse them whenever you want.

This can be done by first creating a folder in your path.

Let's first create a folder with the name "custom_utilities" in your directory


In [17]:
!mkdir "$HOME/custom_utilities"
!mkdir "$HOME/custom_utilities/python_modules/"

The structure of your directory would be:

myMath/
    __init__.py
    adv/
        __init__.py
        sqrt.py
        fib.py
    add.py
    subtract.py
    multiply.py
    divide.py

For example, let's create a module that computes the fibonacci sequence for a given n, under "./modules/adv/fib.py"


In [ ]:
# %load ./modules/adv/fib.py
#! /usr/bin/env python

# NAME
# DATE
# Vanderbilt University
from __future__ import print_function, division, absolute_import
__author__     =['YOUR NAME']
__copyright__  =["Copyright 2017 YOUR NAME, Name of Project"]
__email__      =['Email Address']
__maintainer__ =['Your Name']

__all__["add", "division", "multiply", "subtract"]

from math import sqrt


 
#----------------------------------------------------------------------
def fibonacci(n):
    """
    http://stackoverflow.com/questions/494594/how-to-write-the-fibonacci-sequence-in-python
    """
    return ((1+sqrt(5))**n-(1-sqrt(5))**n)/(2**n*sqrt(5))

And one that computes the square roots for a given number _n_.


In [ ]:
# %load ./modules/adv/sqrt.py
#! /usr/bin/env python

# NAME
# DATE
# Vanderbilt University
from __future__ import print_function, division, absolute_import
__author__     =['YOUR NAME']
__copyright__  =["Copyright 2017 YOUR NAME, Name of Project"]
__email__      =['Email Address']
__maintainer__ =['Your Name']

__all__["add", "division", "multiply", "subtract"]

import math
 
#----------------------------------------------------------------------
def squareroot(n):
    """"""
    return math.sqrt(n)

For each folder and subfolder, you need a "__init__.py" file

This is how it looks like


In [ ]:
# %load ./modules/__init__.py
#! /usr/bin/env python

# NAME
# DATE
# Vanderbilt University
from __future__ import print_function, division#, absolute_import
__author__     =['YOUR NAME']
__copyright__  =["Copyright 2017 YOUR NAME, Name of Project"]
__email__      =['Email Address']
__maintainer__ =['Your Name']

__all__ = ["add", "division", "multiply", "subtract", "fibonacci", "squareroot"]

from arithmetic import add
from arithmetic import division
from arithmetic import multiply
from arithmetic import subtract

from adv.fib import fibonacci
from adv.sqrt import squareroot

And for the one in the adv folder. This file can be empty


In [ ]:
# %load ./modules/adv/__init__.py

The last step to take when creating your own modules is to append your PYTHONPATH.


In [29]:
!echo 'export PYTHONPATH="$HOME/custom_utilities/python_modules/:$PYTHONPATH"' >> $HOME/.bash_profile

Writing a Module Exercise

To wrap up this concept, we can try this with a fresh example that may be useful to have in the future. We can use the rest of the time for writing and debugging a simple Python module that will take an input in hours, minutes, and seconds and return a value that is decimal hours. In this case, we have three values that will be passed in.

What we'll need

  • A function that will do the needed conversion
  • A statement at the end of the code that will be executed if we call the code directly (the code for stellar radius can be thought of as a template for this)