Session Two: Functions, Booleans and Modules

Review/Questions

Review of Previous Session

  • Values and Types
  • Expressions
  • Intro to functions

Homework Review

Any questions that are nagging?

Git Work

Let’s get to know your fellow students!

Working with an Upstream

You’ve created a fork of the class repository from the codefellows account on GitHub.

You’ve pushed your own changes to that fork, and then issued pull requests to have that worked merged back to the codefellows original.

You want to keep your fork up-to-date with that original copy as the class goes forward.

To do this, you use the git concept of an upstream repository.

Since git is a distributed versioning system, there is no central repository that serves as the one to rule them all.

Instead, you work with local repositories, and remotes that they are connected to.

Cloned repositories get an origin remote for free:

$ git remote -v
origin  git@github.com:cewing/sea-c15-python.git (fetch)
origin  git@github.com:cewing/sea-c15-python.git (push)

You can add remotes at will, to connect your local repository to other copies of it in different remote locations.

This allows you to grab changes made to the repository in these other locations.

For our class, we will add an upstream remote to our local copy that points to the original copy of the material in the codefellows account.

$ git remote add upstream https://github.com/codefellows/sea-c15-python.git
$ git remote -v
origin  git@github.com:cewing/sea-c15-python.git (fetch)
origin  git@github.com:cewing/sea-c15-python.git (push)
upstream    https://github.com/codefellows/sea-c15-python.git (fetch)
upstream    https://github.com/codefellows/sea-c15-python.git (push)

To get the updates from your new remote, you’ll need first to fetch everything:

$ git fetch --all
Fetching origin
Fetching upstream
...

Then you can see the branches you have locally available:

$ git branch -a
  gh-pages
* master
  upstream-master
  remotes/origin/HEAD -> origin/master
  ...
  remotes/upstream/gh-pages
  ...

Finally, you can fetch and then merge changes from the upstream master.

Start by making sure you are on your own master branch:

$ git checkout master

This is really really important. Take the time to ensure you are where you think you are.

Then, fetch the upstream master branch and merge it into your master:

$ git fetch upstream master
From github.com:codefellows/sea-c15-python
 * branch            master     -> FETCH_HEAD
$ git merge upstream/master
Updating 137a1db..2119f9b
Fast-forward
...

Now all the changes from upstream are present in your local clone.

In order to preserve them in your fork on GitHub, you’ll have to push:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 10 commits.
  (use "git push" to publish your local commits)
$ git push origin master
Counting objects: 44, done.
...
$

You can incorporate this into your daily workflow:

$ git checkout master
$ git fetch upstream master
$ git merge upstream/master
$ git push origin master
[do some work]
$ git commit -m "here is a good commit message"
$ git push origin master
[make a pull request]

Quick Intro to Basics

Because there’s a few things you just gotta have

Basics

It turns out you can’t really do much at all without at least a container type, conditionals and looping...

if and elif allow you to make decisions:

if a:
    print 'a'
elif b:
    print 'b'
elif c:
    print 'c'
else:
    print 'that was unexpected'

What’s the difference between these two:

if a:
    print 'a'
elif b:
    print 'b'
## versus...
if a:
    print 'a'
if b:
    print 'b'

Many languages have a switch construct:

switch (expr) {
  case "Oranges":
    document.write("Oranges are $0.59 a pound.<br>");
    break;
  case "Apples":
    document.write("Apples are $0.32 a pound.<br>");
    break;
  case "Mangoes":
  case "Papayas":
    document.write("Mangoes and papayas are $2.79 a pound.<br>");
    break;
  default:
    document.write("Sorry, we are out of " + expr + ".<br>");
}

Not Python

use if..elif..elif..else

(or a dictionary, or subclassing....)

A way to store a bunch of stuff in order

called “array” in other languages

a_list = [2,3,5,9]
a_list_of_strings = ['this', 'that', 'the', 'other']

Another way to store an ordered list of things

a_tuple = (2,3,4,5)
a_tuple_of_strings = ('this', 'that', 'the', 'other')

Tuples are not the same as lists.

The exact difference is a topic for next session.

Sometimes called a ‘determinate’ loop

When you need to do something to everything in a sequence

In [10]: a_list = [2,3,4,5]

In [11]: for item in a_list:
   ....:     print item
   ....:
2
3
4
5

Range builds lists of numbers automatically

Use it when you need to do something a set number of times

In [12]: range(6)
Out[12]: [0, 1, 2, 3, 4, 5]

In [13]: for i in range(6):
   ....:     print "*",
   ....:
* * * * * *

This is enough to get you started.

Each of these have intricacies special to python

We’ll get to those over the next couple of classes

Functions

Review

Defining a function:

def fun(x, y):
    z = x+y
    return z

x, y, z are local names

Local vs. Global

Symbols bound in Python have a scope

That scope determines where a symbol is visible, or what value it has in a given block.

In [14]: x = 32
In [15]: y = 33
In [16]: z = 34
In [17]: def fun(y, z):
   ....:     print x, y, z
   ....:
In [18]: fun(3, 4)
32 3 4

x is global, y and z local to the function

But, did the value of y and z change in the global scope?

In [19]: y
Out[19]: 33

In [20]: z
Out[20]: 34

In general, you should use global bindings mostly for constants.

In python we designate global constants by typing the symbols we bind to them in ALL_CAPS

INSTALLED_APPS = [u'foo', u'bar', u'baz']
CONFIGURATION_KEY = u'some secret value'
...

Take a look at this function definition:

In [21]: x = 3

In [22]: def f():
   ....:     y = x
   ....:     x = 5
   ....:     print x
   ....:     print y
   ....:

What is going to happen when we call f

Try it and see:

In [23]: f()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-23-0ec059b9bfe1> in <module>()
----> 1 f()

<ipython-input-22-9225fa53a20a> in f()
      1 def f():
----> 2     y = x
      3     x = 5
      4     print x
      5     print y

UnboundLocalError: local variable 'x' referenced before assignment

Because you are binding the symbol x locally, it becomes a local and masks the global value already bound.

Parameters

So far we’ve seen simple parameter lists:

def fun(x, y, z):
    print x, y, z

These types of parameters are called positional

When you call a function, you must provide arguments for all positional parameters in the order they are listed

You can provide default values for parameters in a function definition:

In [24]: def fun(x=1, y=2, z=3):
   ....:     print x, y, z
   ....:

When parameters are given with default values, they become optional

In [25]: fun()
1 2 3

You can provide arguments to a function call for optional parameters positionally:

In [26]: fun(6)
6 2 3
In [27]: fun(6, 7)
6 7 3
In [28]: fun(6, 7, 8)
6 7 8

Or, you can use the parameter name as a keyword to indicate which you mean:

In [29]: fun(y=4, x=1)
1 4 3

Once you’ve provided a keyword argument in this way, you can no longer provide any positional arguments:

In [30]: fun(x=5, 6)
  File "<ipython-input-30-4529e5befb95>", line 1
    fun(x=5, 6)
SyntaxError: non-keyword arg after keyword arg

This brings us to a fun feature of Python function definitions.

You can define a parameter list that requires an unspecified number of positional or keyword arguments.

The key is the * (splat) or ** (double-splat) operator:

In [31]: def fun(*args, **kwargs):
   ....:     print args, kwargs
   ....:
In [32]: fun(1)
(1,) {}
In [33]: fun(1, 2, zombies="brains")
(1, 2) {'zombies': 'brains'}
In [34]: fun(1, 2, 3, zombies="brains", vampires="blood")
(1, 2, 3) {'vampires': 'blood', 'zombies': 'brains'}

args and kwargs are conventional names for these.

Documentation

It’s often helpful to leave information in your code about what you were thinking when you wrote it.

This can help reduce the number of WTFs per minute in reading it later.

There are two approaches to this:

  • Comments
  • Docstrings

Comments go inline in the body of your code, to explain reasoning:

if (frobnaglers > whozits):
    # borangas are shermed to ensure frobnagler population
    # does not grow out of control
    sherm_the_boranga()

You can use them to mark places you want to revisit later:

for partygoer in partygoers:
    for baloon in baloons:
        for cupcake in cupcakes:
            # TODO: Reduce time complexity here.  It's killing us
            #  for large parties.
            resolve_party_favor(partygoer, baloon, cupcake)

Be judicious in your use of comments.

Use them when you need to.

Make them useful.

This is not useful:

for sponge in sponges:
    # apply soap to each sponge
    worker.apply_soap(sponge)

In Python, docstrings are used to provide in-line documentation in a number of places.

The first place we will see is in the definition of functions.

To define a function you use the def keyword.

If a string literal is the first thing in the function block following the header, it is a docstring:

def complex_function(arg1, arg2, kwarg1=u'bannana'):
    """Return a value resulting from a complex calculation."""
    # code block here

You can then read this in an interpreter as the __doc__ attribute of the function object.

A docstring should:

  • be a complete sentence in the form of a command describing what the function does.
    • “”“Return a list of values based on blah blah”“” is a good docstring
    • “”“Returns a list of values based on blah blah”“” is not
  • fit onto a single line.
    • If more description is needed, make the first line a complete sentence and add more lines below for enhancement.
  • be enclosed with triple-quotes.
    • This allows for easy expansion if required at a later date
    • Always close on the same line if the docstring is only one line.

For more information see PEP 257: Docstring Conventions.

Recursion

You’ve seen functions that call other functions.

If a function calls itself, we call that recursion

Like with other functions, a call within a call establishes a call stack

With recursion, if you are not careful, this stack can get very deep.

Python has a maximum limit to how much it can recurse. This is intended to save your machine from running out of RAM.

Recursion is especially useful for a particular set of problems.

For example, take the case of the factorial function.

In mathmatics, the factorial of an integer is the result of multiplying that integer by every integer smaller than it down to 1.

5! == 5 * 4 * 3 * 2 * 1

We can use a recursive function nicely to model this mathematical function

In-Class Lab:

Fun With Functions

Exercises

Try your hand at writing a function that computes the distance between two points:

dist = sqrt( (x1-x2)**2 + (y1-y2)**2 )

Experiment with locals by adding this statement to the function you just wrote::

print locals()

Boolean Expressions

Truthiness

What is true or false in Python?

  • The Booleans: True and False
  • “Something or Nothing”

http://mail.python.org/pipermail/python-dev/2002-April/022107.html

Determining Truthiness:

bool(something)
  • None
  • False
  • zero of any numeric type: 0, 0L, 0.0, 0j.
  • any empty sequence, for example, "", (), [].
  • any empty mapping, for example, {} .
  • instances of user-defined classes, if the class defines a __nonzero__() or __len__() method, when that method returns the integer zero or bool value False.

http://docs.python.org/library/stdtypes.html

Everything Else

Any object in Python, when passed to the bool() type operator, will evaluate to True or False.

When you use the if keyword, it automatically does this to the statement provided.

Which means that this is redundant, and not Pythonic:

if xx == True:
    do_something()

Instead, use what Python gives you:

if xx:
    do_something()

and, or and not

Python has three boolean keywords, and, or and not.

and and or are binary expressions, and evaluate from left to right.

and will return the first operand that evaluates to False, or the last operand if none are True:

In [35]: 0 and 456
Out[35]: 0

or will return the first operand that evaluates to True, or the last operand if none are True:

In [36]: 0 or 456
Out[36]: 456

On the other hand, not is a unary expression and inverts the boolean value of its operand:

In [39]: not True
Out[39]: False

In [40]: not False
Out[40]: True

Because of the return value of these keywords, you can write concise statements:

                  if x is false,
x or y               return y,
                     else return x

                  if x is false,
x and y               return  x
                      else return y

                  if x is false,
not x               return True,
                    else return False
a or b or c or d
a and b and c and d

The first value that defines the result is returned

This is a fairly common idiom:

if something:
    x = a_value
else:
    x = another_value

In other languages, this can be compressed with a “ternary operator”:

result = a > b ? x : y;

In python, the same is accomplished with the ternary expression:

y = 5 if x > 2 else 3

PEP 308: (http://www.python.org/dev/peps/pep-0308/)

Boolean Return Values

Remember this puzzle from your CodingBat exercises?

def sleep_in(weekday, vacation):
    if weekday == True and vacation == False:
        return False
    else:
        return True

Though correct, that’s not a particularly Pythonic way of solving the problem. Here’s a better solution:

def sleep_in(weekday, vacation):
    return not (weekday == True and vacation == False)

And here’s an even better one:

def sleep_in(weekday, vacation):
    return (not weekday) or vacation

In python, the boolean types are subclasses of integer:

In [1]: True == 1
Out[1]: True
In [2]: False == 0
Out[2]: True

And you can even do math with them (though it’s a bit odd to do so):

In [6]: 3 + True
Out[6]: 4

In-Class Lab:

Better With Booleans

Exercises

  • Look up the % operator. What do these do?
    • 10 % 7 == 3
    • 14 % 7 == 0
  • Write a program that prints the numbers from 1 to 100 inclusive. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz” instead.
  • Re-write a couple of CodingBat exercises, using a conditional expression
  • Re-write a couple of CodingBat exercises, returning the direct boolean results

(use whichever you like, or the ones in: code/codingbat.rst )

Code Structure, Modules, and Namespaces

How to get what you want when you want it.

Code Structure

In Python, the structure of your code is determined by whitespace.

How you indent your code determines how it is structured

block statement:
    some code body
    some more code body
    another block statement:
        code body in
        that block

The colon that terminates a block statement is also important...

You can put a one-liner after the colon:

In [167]: x = 12
In [168]: if x > 4: print x
12

But this should only be done if it makes your code more readable.

Whitespace is important in Python.

An indent could be:

  • Any number of spaces
  • A tab
  • A mix of tabs and spaces:

If you want anyone to take you seriously as a Python developer:

Always use four spaces – really!

(PEP 8)

Other than indenting – space doesn’t matter, technically.

x = 3*4+12/func(x,y,z)
x = 3*4 + 12 /   func (x,   y, z)

But you should strive for proper style. Read PEP 8 and install a linter in your editor.

Modules and Packages

Python is all about namespaces – the “dots”

name.another_name

The “dot” indicates that you are looking for a name in the namespace of the given object. It could be:

  • name in a module
  • module in a package
  • attribute of an object
  • method of an object

A module is simply a namespace.

It might be a single file, or it could be a collection of files that define a shared API.

To a first approximation, you can think of the files you write that end in .py as modules.

A package is a module with other modules in it.

On a filesystem, this is represented as a folder that contains one or more .py files, one of which must be called __init__.py.

When you have a package, you can import the package, or any of the modules inside it.

import modulename
from modulename import this, that
import modulename as a_new_name
from modulename import this as that
import packagename.modulename
from packagename.modulename import this, that
from package import modulename

http://effbot.org/zone/import-confusion.htm

from modulename import *

Don’t do this!

Import

When you import a module, or a symbol from a module, the Python code is compiled to bytecode.

The result is a module.pyc file.

This process executes all code at the module scope.

For this reason, it is good to avoid module-scope statements that have global side-effects.

The code in a module is NOT re-run when imported again

It must be explicitly reloaded to be re-run

import modulename
reload(modulename)

In addition to importing modules, you can run them.

There are a few ways to do this:

  • $ python hello.py – must be in current working directory
  • $ python -m hello – any module on PYTHONPATH anywhere on the system
  • $ ./hello.py – put #!/usr/env/python at top of module (Unix)
  • run hello.py – at the IPython prompt – running a module brings the names into the interactive namespace

Like importing, running a module executes all statements at the module level.

But there’s an important difference.

When you import a module, the value of the symbol __name__ in the module is the same as the filename.

When you run a module, the value of the symbol __name__ is __main__.

This allows you to create blocks of code that are executed only when you run a module

if __name__ == '__main__':
    # Do something interesting here
    # It will only happen when the module is run

This is useful in a number of cases.

You can put code here that lets your module be a utility script

You can put code here that demonstrates the functions contained in your module

You can put code here that proves that your module works.

Writing tests that demonstrate that your program works is an important part of learning to program.

The python assert statement is useful in writing main blocks that test your code.

In [1]: def add(n1, n2):
   ...:     return n1 + n2
   ...:

In [2]: assert add(3, 4) == 7

In [3]: assert add(3, 4) == 10
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-3-6731d4ac4476> in <module>()
----> 1 assert add(3, 4) == 10

AssertionError:

In-Class Lab

Import Interactions

Exercises

Experiment with importing different ways:

In [3]: import math

In [4]: math.<TAB>
math.acos       math.degrees    math.fsum       math.pi
math.acosh      math.e          math.gamma      math.pow
math.asin       math.erf        math.hypot      math.radians
math.asinh      math.erfc       math.isinf      math.sin
math.atan       math.exp        math.isnan      math.sinh
math.atan2      math.expm1      math.ldexp      math.sqrt
math.atanh      math.fabs       math.lgamma     math.tan
math.ceil       math.factorial  math.log        math.tanh
math.copysign   math.floor      math.log10      math.trunc
math.cos        math.fmod       math.log1p
math.cosh       math.frexp      math.modf
In [6]: math.sqrt(4)
Out[6]: 2.0
In [7]: import math as m
In [8]: m.sqrt(4)
Out[8]: 2.0
In [9]: from math import sqrt
In [10]: sqrt(4)
Out[10]: 2.0

Experiment with importing different ways:

import sys
print sys.path
import os
print os.path

You wouldn’t want to import * those – check out

os.path.split('/foo/bar/baz.txt')
os.path.join('/foo/bar', 'baz.txt')

Homework

You have two tasks to complete by Wednesday:

Task 1

The Ackermann function, A(m, n), is defined:

A(m, n) =
    n+1   if  m = 0
    A(m−1, 1)   if  m > 0  and  n = 0
    A(m−1, A(m, n−1))   if  m > 0  and  n > 0.

See http://en.wikipedia.org/wiki/Ackermann_function.

Create a new module called ack.py in your student folder. In that module, write a function named ack that performs Ackermann’s function.

  • Write a good docstring for your function according to PEP 257.
  • Ackermann’s function is not defined for input values less than 0. Validate inputs to your function and return None if they are negative.

The wikipedia page provides a table of output values for inputs between 0 and 4. Using this table, add a if __name__ == "__main__": block to test your function.

Test each pair of inputs between 0 and 4 and assert that the result produced by your function is the result expected by the wikipedia table.

When your module is run from the command line, these tests should be executed. If they all pass, print “All Tests Pass” as the result.

Add your new module to your git clone and commit frequently while working on your implementation. Include good commit messages that explain concisely both what you are doing and why.

When you are finished, push your changes to your fork of the class repository in GitHub. Then make a pull request and submit your assignment in canvas.

- Adapted from "Think Python": Chapter 6, excercise 5.

Task 2

The Fibonacci Series is a numeric series starting with the integers 0 and 1. In this series, the next integer is determined by summing the previous two. This gives us:

0, 1, 1, 2, 3, 5, 8, 13, ...

Create a new module series.py in your student folder. In it, add a function called fibonacci. The function should have one parameter n. The function should return the nth value in the fibonacci series.

Ensure that your function has a well-formed docstring

The Lucas Numbers are a related series of integers that start with the values 2 and 1 rather than 0 and 1. The resulting series looks like this:

2, 1, 3, 4, 7, 11, 18, 29, ...

In your series.py module, add a new function lucas that returns the nth value in the lucas numbers

Ensure that your function has a well-formed docstring

Both the fibonacci series and the lucas numbers are based on an identical formula.

Add a third function called sum_series with one required parameter and two optional parameters. The required parameter will determine which element in the series to print. The two optional parameters will have default values of 0 and 1 and will determine the first two values for the series to be produced.

Calling this function with no optional paramters will produce numbers from the fibonacci series. Calling it with the optional arguments 2 and 1 will produce values from the lucas numbers. Other values for the optional parameters will produce other series.

Ensure that your function has a well-formed docstring

Add an if __name__ == "__main__": block to the end of your series.py module. Use the block to write a series of assert statements that demonstrate that your three functions work properly.

Use comments in this block to inform the observer what your tests do.

Add your new module to your git clone and commit frequently while working on your implementation. Include good commit messages that explain concisely both what you are doing and why.

When you are finished, push your changes to your fork of the class repository in GitHub. Then make a pull request and submit your assignment in Canvas.