In [1]:
from IPython.display import Image

Lecture 5:

  • Learn about functions

  • Discover the joys of modules

Code re-use

You can imagine that there are many bits of code that you'll want to re-use. For example, in Python the trigonometry functions require radians, but we Earth Scientists typically think in terms of degress. It would be helpful to convert back and forth between degrees and radians. Or, you might want to find the great circle distance between two points on Earth, or convert between Universal Transverse Mercator (UTM - a special map projection with units of meters) and the more familiar latitude and longitude coordinates. This is where Python functions are useful. You can define a bit of code, assign it a name and a list of input arguments, and then keep re-using it anytime you would like. Or, you can save it to a file in your Python directory (see Lecture 1) and import it into any notebook or script any time you like.

We've already used built-in functions, for example list( ), which takes a list generator as an argument and then makes an actual list out of it. Now it is time to learn how to write your own functions.

The most common structure of a Python function is to supply some input (IN), do something to the input (RULE), then return the output (OUT).

In [2]:
Image('Figures/function-machine.png')
Out[2]:

Here is an example of a Python function:

In [5]:
def FUNCNAME(in_args):  
    """   
    DOC STRING    
    """
    out_args=in_args # ok - not very fancy....   
    
    return out_args

Line by line analysis

def FUNCNAME(in_args):

def

  • the first line of a function must have def as the first three letters, it warns Python that you're defining a function

FUNCNAME

  • def must be followed by the name of the function

in_args

  • This is the equivalent of the left 'arm' of the function in the cartoon, labelled 'IN'. The objects in the parentheses are called arguments. One or more arguments can be passed to the function when it's called and are specified between the parentheses. In this example, in_args (a single variable) is passed to the function FUNCNAME. In general, input arguments can range from none (e.g., FUNCNAME( )) to many comma separated variables (e.g., FUNCNAME(variable_1, variable_2, variable_3)). It is also possible to have optional variables, but we will get to that later.

:

  • the first line of a function always ends with a terminal colon

"""
DOC STRING
"""



The line or lines between the triple quotes right after the function definition line is a doc string. Here you write a description of what the function does, which can be accessed later by using the by now familiar help( ) function.

help(FUNCNAME) will print out the doc string.

Make a habit of using the doc string to explain what the function does. It is most helpful to have an explanation of what the input arguments are (type of object and what it is for) and what the returned objects are.


The body of the function does something.


There is an optional return statement that can return the results of whatever the function did.

In [3]:
help(FUNCNAME)
Help on function FUNCNAME in module __main__:

FUNCNAME(in_args)
    DOC STRING

The Doc String

Although you can certainly write functional code without a document string, make a habit of always including one. Trust me - you'll be glad you did and anyone using your code will be super grateful for any hints you give. The Doc String briefly describes what the code does; weeks after you've written your code, it will remind you of what you did. In addition, you can use it to print out a help message that lets others know what the program does. You should define what the input and output variables are and what the function does.

To make this point a little more clearly, you will LOSE POINTS ON YOUR HOMEWORK IF YOU DO NOT PUT DOC STRINGS INTO YOUR FUNCTIONS. Ok, that is a bit hard ball, but it will help you make good programming habits.

Notice the use of the triple quotes before and after the documentation string - this means that you can write as many lines as you want.

Function body

This is equivalanet to the body of the cartoon function labelled 'RULE'. This part of the function code must be indented, just like in a for loop, or other block of code.
The code can be as simple as pass which does nothing. Our example function just assigns the variable $in\_args$ to a new variable name $out\_args$.

Return statement

Python separates the input and output arguments. Incoming arguments are passed in through the def statement ($in\_args$ in the example function) and returning arguments get shipped out with the return statement, which is the equivalent of the 'OUT' arm in our cartoon function. In the example function, $out\_args$ get returned to the caller.

So, let's try it out:

In [7]:
# this calls the function with the input variables, in_args

    
print (FUNCNAME(4))

# or alternatively: 

returned_value=FUNCNAME(4)
print (returned_value)
4
4

There are many more sophisticated possibilities, but for now, here is a simpler one with
no input arguments, just a return statement:

In [4]:
def gimmepi():  # define the function
    """
    returns the value of pi     
    """
    return 3.141592653589793
print (gimmepi())

twoPi = 2*gimmepi()
print (twoPi)
3.141592653589793
6.283185307179586

And let's try out the help function for more practice:

In [5]:
help(gimmepi)
Help on function gimmepi in module __main__:

gimmepi()
    returns the value of pi

Passing arguments

There are three different ways to pass arguments into a function:

1) You could use a fixed number of arguments

This function has only one argument:

In [17]:
def degrees_to_radians(degrees):  
    """
    converts degrees to radians
    """
    return degrees*gimmepi()/180.
print ('42 degrees in radians is: ',degrees_to_radians(42))
42 degrees in radians is:  0.7330382858376184

2) Another way to pass arguments is with a variable number of input arguments. Functions that accept a variable number of arguments are called variadic functions. You do this by putting *args at the end of the list of required arguments if any.

In [18]:
def print_args(*args):
    """
    prints argument list
    """
    print (args)  # args is a tuple that you can step through (like a list)
    print ('You sent me these arguments: ')
    for arg in args: # steps through the argument tuple
        print (arg)
print_args(1,4,'hi there')
print_args(42)
(1, 4, 'hi there')
You sent me these arguments: 
1
4
hi there
(42,)
You sent me these arguments: 
42

Notice how the arguments are actually passed as a tuple.... (One very common use of tuples).

3) Another way to pass in arguments is to use any number of keyword-value pairs. This is done by putting **kwargs as the last argument in the argument list. kwargs stands for key word arguments and is treated like a list in the function.

In [8]:
def print_keyword_arguments(**kwargs):
    """
    prints keyword argument list
    """
    print (kwargs) # kwargs is a dictionary with key:value pairs
    for key in kwargs:
        print (key, kwargs[key])
    
print_keyword_arguments(arg1='spam',arg2=42,arg3='ocelot')
# ocelot is another Monty Python joke about Brian (from Life of Brian) trying to 
# sell "Larks' tongues, Otters' noses and Ocelot spleens.  yummy. 
{'arg1': 'spam', 'arg2': 42, 'arg3': 'ocelot'}
arg1 spam
arg2 42
arg3 ocelot

Main program as a function

It is considered good Python style to treat your main program block as a function too. This helps with using the document string as a help function and building program documentation in general. In any case, I recommend that you just start doing it that way too. In this case, we have to call the main program with the final (not indented) line

main( )

See how it is done in the following:

In [9]:
def print_keyword_arguments(**kwargs): # same function as before
    """
    prints keyword argument list
    """
    for key in kwargs:
        print (key, kwargs[key])  

def main(): # new function called "main"
    """
    calls function print_kwargs
    """
    print_keyword_arguments(arg1='spam',arg2=42,arg3='ocelot')
    
main()  # runs the main program
arg1 spam
arg2 42
arg3 ocelot

Notice how all the functions precede the main( ) function. This is because Python is not compiled and executes things in order. All of the functions and variables must be defined before they're called, otherwise the interpreter won't recognize them.

You may wonder how we've called functions (e.g., str( ), int( ), and float( )) that we did not define in our script. These are more built in Python functions that are accessible to every Python script.

Modules

What if you wanted to use functions that are defined in a different script written either by future you or by some other helpful soul?

You can define many functions in a separate script called a module, and import that script into your current script. Then you can call those functions in the module from within your program.

So let's say I put some functions in a file called myfuncs.py. To do this, we can use the %%writefile magic Jupyter notebook command you learned about in Lecture 1.

In [10]:
%%writefile myfuncs.py
## module myfuncs
def gimmepi():  
    """
    returns pi
    """
    return 3.141592653589793
def degrees_to_radians(degrees):  
    """
    converts degrees to radians
    """
    return degrees*3.141592653589793/180.
def print_args(*args):
    """
    prints argument list
    """
    print ('You sent me these arguments: ')
    for arg in args:
        print (arg)
Writing myfuncs.py

By the way, there are many so-called magic commands available in a Jupyter notebook. For a complete list, just type:

In [2]:
%lsmagic
Out[2]:
Available line magics:
%alias  %alias_magic  %autoawait  %autocall  %automagic  %autosave  %bookmark  %cat  %cd  %clear  %colors  %config  %connect_info  %cp  %debug  %dhist  %dirs  %doctest_mode  %ed  %edit  %env  %gui  %hist  %history  %killbgscripts  %ldir  %less  %lf  %lk  %ll  %load  %load_ext  %loadpy  %logoff  %logon  %logstart  %logstate  %logstop  %ls  %lsmagic  %lx  %macro  %magic  %man  %matplotlib  %mkdir  %more  %mv  %notebook  %page  %pastebin  %pdb  %pdef  %pdoc  %pfile  %pinfo  %pinfo2  %popd  %pprint  %precision  %prun  %psearch  %psource  %pushd  %pwd  %pycat  %pylab  %qtconsole  %quickref  %recall  %rehashx  %reload_ext  %rep  %rerun  %reset  %reset_selective  %rm  %rmdir  %run  %save  %sc  %set_env  %store  %sx  %system  %tb  %time  %timeit  %unalias  %unload_ext  %who  %who_ls  %whos  %xdel  %xmode

Available cell magics:
%%!  %%HTML  %%SVG  %%bash  %%capture  %%debug  %%file  %%html  %%javascript  %%js  %%latex  %%markdown  %%perl  %%prun  %%pypy  %%python  %%python2  %%python3  %%ruby  %%script  %%sh  %%svg  %%sx  %%system  %%time  %%timeit  %%writefile

Automagic is ON, % prefix IS NOT needed for line magics.

Now you can import myfuncs, which will make all your hard work available to you in your notebook. To access a particular function, use the syntax module.function( ). Here is an example:

In [11]:
import myfuncs # this will import your module
myfuncs.gimmepi()  # this just runs the gimmepi() function.  
Out[11]:
3.141592653589793

And some other things become possible like getting the document string. You can use help( ) as before, or you can get the doc string this way:

In [12]:
print (myfuncs.print_args.__doc__) # note that the __ is actually two underscores - not one
    prints argument list
    

TIP: One thing that you might want to do is modify a module you are writing and then re-import it. This used to be possible with a built-in function reload( ). But now we have to import reload from the module importlib like this:

In [15]:
from importlib import reload

Now you can change your myfuncs.py module and reload it like this:

In [16]:
%%writefile myfuncs.py
## module myfuncs
def gimmepi():  
    """
    returns the value of pi
    """
    return 3.141592653589793
def degrees_to_radians(degrees):  
    """
    converts degrees to radians
    """
    return degrees*3.141592653589793/180.
def print_args(*args):
    """
    prints out argument list
    """
    print ('The arguments were: ')
    for arg in args:
        print (arg)
Overwriting myfuncs.py
In [17]:
reload(myfuncs)
help(gimmepi)
Help on function gimmepi in module __main__:

gimmepi()
    returns the value of pi

Scope of variables

Inside a function, variable names have their own meaning which in many cases will be different from outside the calling function. So, variables names declared inside a function stay in the function.

In [24]:
def LasVegas():
    V=123
def main():
    LasVegas()
    print (V)
main()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-24-43b89960ac32> in <module>
      4     LasVegas()
      5     print (V)
----> 6 main()
      7 

<ipython-input-24-43b89960ac32> in main()
      3 def main():
      4     LasVegas()
----> 5     print (V)
      6 main()
      7 

NameError: name 'V' is not defined

What happened in LasVegas stayed in LasVegas because V was defined in the function and doesn't exist outside.

This is true unless you declare a variable to be global. Then it is known outside the function.

Here is an example in which the main program "knows" about the function's variable V.

In [25]:
def SanDiego():
    global V
    V=123
def main():
    SanDiego()
    print (V)
main()
123

oops - what happened in SanDiego didn't stay in San Diego.

In addition to writing your own functions, modules, and programs, Python has many built-in functions and modules that you can import. We already encountered a few built in functions (for example int( )) but there are many more. And there are many more modules which we will use shortly. We'll spend the quarter exploring some of these modules, which include plotting, numerical recipes, trig functions, image manipulation, animation, and more.

Now open your Practice Problems for this lecture, complete the tasks and turn it in before the end of class.

In [18]:
# a little housekeeping
!rm myfuncs.py
In [ ]: