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_2014/) course. Source and license info is on [GitHub](https://github.com/jakevdp/2014_fall_ASTR599/).
In [1]:
%run talktools.py
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 [3]:
def addnums(x, y):
return x + y
In [5]:
result = addnums(1, 2)
result
Out[5]:
In [6]:
addnums(1, y=2)
Out[6]:
In [8]:
addnums("A", "B")
Out[8]:
Note that the variable types are not declared (as we've discussed Python is a dynamic language)
In [9]:
def scale(x, factor=2.0):
return x * factor
In [10]:
scale(4)
Out[10]:
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 [14]:
def build_dict(x, y):
return {'x':x, 'y':y}
build_dict(4, 5)
Out[14]:
In [17]:
def no_return_value():
pass
x = no_return_value()
print(x)
In [18]:
def build_dict(x, y):
return {'x':x, 'y':y}
build_dict(1, 2)
Out[18]:
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 [19]:
def build_dict(x, y, xname='x', yname='y'):
return {xname:x, yname:y}
build_dict(1, 2) # old call still works
Out[19]:
In [20]:
build_dict(1, 2, xname='spam', yname='eggs')
Out[20]:
This is admittedly a silly example, but it shows how keywords can be used to add flexibility without breaking old APIs.
In [21]:
def modify_x(x):
x += 5
return x
In [23]:
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 [27]:
def add_one(x):
x += 1
x = 4
add_one(x)
print(x)
In [28]:
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 [30]:
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 [31]:
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 [32]:
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[32]:
(Note that this example follows the Numpy documentation standard)
With documentation specified this way, the IPython help command will be helpful!
In [33]:
power_of_difference?
In [34]:
%%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 [35]:
# Pydoc is a command-line program bundled with Python
!pydoc -w myfile
In [36]:
from IPython.display import HTML
HTML(open('myfile.html').read())
Out[36]:
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 [44]:
%%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 [45]:
import mymodule
In [48]:
print('1 + 2 =', mymodule.add_numbers(1, 2))
print('5 - 3 =', mymodule.subtract_numbers(5, 3))
Note that namespaces are important:
In [49]:
add_numbers(1, 2)
In [50]:
import mymodule
mymodule.add_numbers(1, 2)
Out[50]:
In [51]:
from mymodule import add_numbers
add_numbers(1, 2)
Out[51]:
In [53]:
from mymodule import add_numbers as new_name
new_name(1, 2)
Out[53]:
In [54]:
from mymodule import *
subtract_numbers(5, 3)
Out[54]:
This final method can be convenient, but should generally be avoided as it can cause name collisions and makes debugging difficult.
In [57]:
%%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 [58]:
import mymodule2
In [59]:
# import again and the initial code does not execute!
import mymodule2
In [60]:
# access module-level documentation
mymodule2?
In [62]:
mymodule2.multiply(2, 3)
Out[62]:
In [63]:
mymodule2.pi
Out[63]:
In [64]:
# module variables can be modified
mymodule2.favorite_food
Out[64]:
In [65]:
mymodule2.favorite_food = "eggs. No spam."
mymodule2.favorite_food
Out[65]:
In [67]:
import sys
sys?
In [68]:
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 [69]:
# 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 [74]:
%%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 ", 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 [75]:
# 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