#!/usr/bin/env python # coding: utf-8 # Just click on the cell below if you want to see the images in codeblocks that say "Image(...)". # In[1]: from IPython.display import Image import os # ### 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 use over and over again. For example, in Python the trigonometry functions require radians, but we Earth Scientists typically think in terms of degrees. 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 come in handy. 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 like. Or, you can save it to a file in your _Python_ code directory (see Lecture 1) and import it into any notebook or command line 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') # _Figure from:_ https://www.google.com/search?q=the+function+machine&safe=active&client=firefox-b-1-ab&source=lnms&tbm=isch&sa=X&ved=0ahUKEwjVks-88YTgAhUIjFQKHQVPApYQ_AUIDigB&biw=1155&bih=937&dpr=1#imgrc=QpP0BPO8PBd4PM: # Here is an example of a Python function: # In[3]: 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[4]: help(FUNCNAME) # ### 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. I like the form: # # """ # This function doesn't do much. # # Parameters: # # --------------- # in_args : str # # The input string # # Returns: # # --------------- # # out_args : str # # The output string (in this case the same as the input string). # """ # # # 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 labeled 'RULE'. This part of the function code must be indented, just like in a **for** loop, or other blocks 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$ gets returned to the caller. # # So, let's try it out: # # In[5]: # this calls the function with the input variables, in_args print (FUNCNAME('Four')) # or alternatively: returned_value=FUNCNAME('Four') print (returned_value) # There are many more sophisticated possibilities, but for now, here is a simple one with # no input arguments, just a **return** statement: # In[6]: def gimmepi(): # define the function """ returns the value of pi Parameters: ____________ None Returns: ___________ The value of pi """ return 3.141592653589793 print (gimmepi()) twoPi = 2*gimmepi() print (twoPi) # And let's try out the help function for more practice: # In[7]: help(gimmepi) # ### 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[8]: def degrees_to_radians(degrees): """ converts degrees to radians Parameters: ___________ degrees : float degrees to convert to radians Returns: ________ radians : float degrees converted to radians """ radians=degrees*gimmepi()/180. return radians print ('42 degrees in radians is: ',degrees_to_radians(42)) # 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[9]: def print_args(*args): """ prints argument list Parameters: ____________ *args : tuple of input arguments Returns: ________ None """ 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) # 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 **dictionary** in the function. # # # # In[10]: def print_keyword_arguments(**kwargs): """ prints keyword argument list Parameters: _____________ **kwargs : dict input dictionary of arguments Returns: ________ None """ 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. # ### 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[11]: def print_keyword_arguments(**kwargs): # same function as before """ prints keyword argument list Parameters: _____________ **kwargs : dict input dictionary of arguments Returns: ________ None """ 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 # # 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[12]: get_ipython().run_cell_magic('writefile', 'myfuncs.py', '## module myfuncs\ndef gimmepi(): \n """\n returns the value of pi\n \n Parameters:\n ____________\n None\n Returns:\n ___________\n The value of pi\n """\n return 3.141592653589793\ndef degrees_to_radians(degrees): \n """\n converts degrees to radians\n Parameters:\n ___________\n degrees : float\n degrees to convert to radians\n Returns:\n ________\n radians : float\n degrees converted to radians\n """\n radians=degrees*3.141592653589793/180.\n return radians\ndef print_args(*args):\n """\n prints argument list\n Parameters:\n ____________\n *args : tuple of input arguments\n Returns: \n ________\n None\n """\n print (\'You sent me these arguments: \')\n for arg in args:\n print (arg)\n') # By the way, there are many so-called _magic_ commands available in a Jupyter notebook. For a complete list, just type: # In[13]: get_ipython().run_line_magic('lsmagic', '') # 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[14]: import myfuncs # this will import your module myfuncs.gimmepi() # this just runs the gimmepi() function. # 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[15]: print (myfuncs.print_args.__doc__) # note that the __ is actually two underscores - not one # **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[16]: from importlib import reload # Now you can change your **myfuncs.py** module and reload it like this: # In[17]: get_ipython().run_cell_magic('writefile', 'myfuncs.py', '## module myfuncs\ndef gimmepi(): \n """\n returns the value of pi\n """\n return 3.141592653589793\ndef degrees_to_radians(degrees): \n """\n converts degrees to radians\n """\n return degrees*3.141592653589793/180.\ndef print_args(*args):\n """\n prints out argument list\n """\n print (\'The arguments were: \')\n for arg in args:\n print (arg)\n') # In[18]: reload(myfuncs) help(gimmepi) # ### 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[19]: def LasVegas(): V=123 def main(): LasVegas() print (V) main() # 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[20]: def SanDiego(): global V V=123 def main(): SanDiego() print (V) main() # 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. # In[21]: # a little housekeeping os.remove('myfuncs.py')