This notebook was put together by [Jake Vanderplas](http://www.vanderplas.com) for UW's [Astro 599](http://www.astro.washington.edu/users/vanderplas/Astr599/) course. Source and license info is on [GitHub](https://github.com/jakevdp/2013_fall_ASTR599/).
An important part of coding (in Python and in other modern language) is organizing code in easily re-used chunks.
Python can work within both a procedural and an object-oriented style.
Procedural programming is using functions
Object-oriented programming is using classes
We'll come back to classes later, and look at functions now.
The function name can be anything, as long as it:
print, or for)Note for IDL users: there is no difference between functions and procedures. All Python functions return a value: if no return is specified, it returns None
In [4]:
def addnums(x, y):
return x + y
In [5]:
result = addnums(1, 2)
print result
In [6]:
print addnums(1, y=2)
In [7]:
print addnums("A", "B")
Note that the variable types are not declared (as we've discussed Python is a dynamic language)
In [8]:
def scale(x, factor=2.0):
return x * factor
In [9]:
scale(4)
Out[9]:
In [11]:
scale(4, 10)
Out[11]:
In [12]:
scale(4, factor=10)
Out[12]:
Arguments and Keyword arguments can either be specified by order or by name, but an unnamed argument cannot come after a named argument:
In [13]:
scale(x=4, 10)
In [26]:
def build_dict(x, y):
return {'x':x, 'y':y}
build_dict(4, 5)
Out[26]:
In [27]:
def no_return_value():
pass
x = no_return_value()
print x
In [28]:
def build_dict(x, y):
return {'x':x, 'y':y}
build_dict(1, 2)
Out[28]:
Now what if you want to change the names of the variables in the dictionary? Adding a keyword argument can allow this flexibility without breaking old code:
In [29]:
def build_dict(x, y, xname='x', yname='y'):
return {xname:x, yname:y}
build_dict(1, 2) # old call still works
In [30]:
build_dict(1, 2, xname='spam', yname='eggs')
Out[30]:
This is admittedly a silly example, but it shows how keywords can be used to add flexibility without breaking old APIs.
In [14]:
def modify_x(x):
x += 5
return x
In [15]:
x = 10
y = modify_x(x)
print x
print y
Modifying a variable in the function does not modify the variable globally... unless you use the global declaration
In [25]:
def add_a(x):
global a
a += 1
return x + a
a = 10
print add_a(5)
print a
In [18]:
def add_one(x):
x += 1
x = 4
add_one(x)
print x
In [19]:
def add_element(L):
L.append(4)
L = [1, 2]
add_element(L)
print L
Simple types (int, long, float, complex, string) are passed by value.
Compound types (list, dict, set, tuple, user-defined objects) are passed by reference.
Question to think about: why would this be?
In [1]:
def cheeseshop(kind, *args, **kwargs):
print "Do you have any", kind, "?"
print "I'm sorry, we're all out of", kind
for arg in args:
print arg
print 40 * "="
for kw in kwargs:
print kw, ":", kwargs[kw]
In [2]:
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
(example from Python docs)
In [38]:
def power_of_difference(x, y, p=2.0):
"""Return the power of the difference of x and y
Parameters
----------
x, y : float
the values to be differenced
p : float (optional)
the exponent (default = 2.0)
Returns
-------
result: float
(x - y) ** p
"""
diff = x - y
return diff ** p
power_of_difference(10.0, 5.0)
Out[38]:
(Note that this example follows the Numpy documentation standard)
With documentation specified this way, the IPython help command will be helpful!
In [39]:
power_of_difference?
In [41]:
%%file myfile.py
def power_of_difference(x, y, p=2.0):
"""Return the power of the difference of x and y
Parameters
----------
x, y : float
the values to be differenced
p : float (optional)
the exponent (default = 2.0)
Returns
-------
result: float
(x - y) ** p
"""
diff = x - y
return diff ** p
In [42]:
# Pydoc is a command-line program bundled with Python
!pydoc -w myfile
In [46]:
from IPython.display import HTML
HTML(open('myfile.html').read())
Out[46]:
Modules are organized units of code which contain functions, classes, statements, and other definitions.
Any file ending in .py is treated as a module (e.g. our file myfile.py above).
Variables in modules have their own scope: using a name in one module will not affect variables of that name in another module.
In [49]:
%%file mymodule.py
# A simple demonstration module
def add_numbers(x, y):
"""add x and y"""
return x + y
def subtract_numbers(x, y):
"""subtract y from x"""
return x - y
Modules are accessed using import module_name (with no .py)
In [50]:
import mymodule
In [52]:
print '1 + 2 =', mymodule.add_numbers(1, 2)
print '5 - 3 =', mymodule.subtract_numbers(5, 3)
In [53]:
print add_numbers(1, 2)
In [54]:
import mymodule
mymodule.add_numbers(1, 2)
Out[54]:
In [55]:
from mymodule import add_numbers
add_numbers(1, 2)
Out[55]:
In [57]:
from mymodule import add_numbers as silly_function
silly_function(1, 2)
Out[57]:
In [56]:
from mymodule import *
subtract_numbers(5, 3)
Out[56]:
This final method can be convenient, but should generally be avoided as it can cause name collisions and makes debugging difficult.
In [58]:
%%file mymodule2.py
"""
Example module with some variables and startup code
"""
# this code runs when the module is loaded
print "mymodule2 in the house!"
pi = 3.1415926
favorite_food = "spam, of course"
def multiply(a, b):
return a * b
In [65]:
import mymodule2
In [66]:
# import again and the initial code does not execute!
import mymodule2
In [67]:
# access module-level documentation
mymodule2?
In [61]:
print mymodule2.multiply(2, 3)
print mymodule2.pi
In [68]:
# module variables can be modified
print mymodule2.favorite_food
In [69]:
mymodule2.favorite_food = "eggs. No spam."
print mymodule2.favorite_food
In [72]:
import sys
help(sys)
In [74]:
import sys
import os
print "You are using Python version", sys.version
print 40 * '-'
print "Current working directory is:"
print os.getcwd()
print 40 * '-'
print "Files in the current directory:"
for f in os.listdir(os.getcwd()):
print f
Built-in modules are listed at http://docs.python.org/2/py-modindex.html
In [ ]:
# try importing antigravity...
When a script or module is run directly from the command-line (i.e. not imported) a special variable called __name__ is set to "__main__".
So, in your module, if you want some part of the code to only run when the script is executed directly, then you can make it look like this:
# all module stuff
# at the bottom, put this:
if __name__ == '__main__':
# do some things
print "I was called from the command-line!"
Here's a longer example of this in action:
In [4]:
%%file modfun.py
"""
Some functions written to demonstrate a bunch of concepts
like modules, import and command-line programming
"""
import os
import sys
def getinfo(path=".",show_version=True):
"""
Purpose: make simple us of os and sys modules
Input: path (default = "."), the directory you want to list
"""
if show_version:
print "-" * 40
print "You are using Python version ",
print sys.version
print "-" * 40
print "Files in the directory " + str(os.path.abspath(path)) + ":"
for f in os.listdir(path):
print " " + f
print "*" * 40
if __name__ == "__main__":
"""
Executed only if run from the command line.
call with
modfun.py <dirname> <dirname> ...
If no dirname is given then list the files in the current path
"""
if len(sys.argv) == 1:
getinfo(".",show_version=True)
else:
for i,dir in enumerate(sys.argv[1:]):
if os.path.isdir(dir):
# if we have a directory then operate on it
# only show the version info
# if it's the first directory
getinfo(dir,show_version=(i==0))
else:
print "Directory: " + str(dir) + " does not exist."
In [6]:
# now execute from the command-line
%run modfun.py
Note some of the sys and os commands used in this script!
This breakout will give you a chance to explore some of the builtin modules offered by Python. For this session, please use your text editor to create the files. You'll have to
Create and edit a new file called age.py. Though you can do this via the %%file magic used above, here you should use your text editor.
age.py, import the datetime moduledatetime.datetime() to create a variable representing your birthdaydatetime.datetime.now() to create a variable representing the present datesubtract the two (this forms a datetime.timedelta() object) and print that variable.
Use this object to answer these questions:
How many days have you been alive?
How many hours have you been alive?
What will be the date 1000 days from now?
Create and edit a new file called age1.py. When run from the command-line with one argument, age1.py should print out the date in that many days from now. If run with three arguments, print the time in days since that date.
[~]$ python age1.py 1000
date in 1000 days 2016-06-06 14:46:09.548831
[~]$ python age1.py 1981 6 12
days since then: 11779