Python Course Outline

Author: Dave Kuhlman
Address:
dkuhlman@rexx.com
http://www.rexx.com/~dkuhlman
Revision: 1.3b
Date: February 03, 2010
Copyright:Copyright (c) 2007 Dave Kuhlman. All Rights Reserved. This software is subject to the provisions of the MIT License http://www.opensource.org/licenses/mit-license.php.
Abstract:This document provides notes and an outline of an introductory course on programming in Python.

Contents

1   Introductions Etc

Introductions

Practical matters

Starting the Python interactive interpreter. Also, IPython and Idle.

Running scripts

Editors -- Choose an editor which you can configure so that it indents with 4 spaces, not tab characters. For a list of editors for Python, see: http://wiki.python.org/moin/PythonEditors. A few possible editors:

Interactive interpreters:

IDEs -- Also see http://en.wikipedia.org/wiki/List_of_integrated_development_environments_for_Python:

1.1   Resources

Where else to get help:

Local documentation:

  • On MS Windows, the Python documentation is installed with the standard installation.

  • Install the standard Python documentation on your machine from http://www.python.org/doc/.

  • pydoc. Example, on the command line, type: pydoc re.

  • Import a module, then view its .__doc__ attribute.

  • At the interactive prompt, use help(obj). You might need to import it first. Example:

    >>> import urllib
    >>> help(urllib)
    
  • In IPython, the question mark operator gives help. Example:

    In [13]: open?
    Type:           builtin_function_or_method
    Base Class:     <type 'builtin_function_or_method'>
    String Form:    <built-in function open>
    Namespace:      Python builtin
    Docstring:
        open(name[, mode[, buffering]]) -> file object
    
        Open a file using the file() type, returns a file object.
    Constructor Docstring:
        x.__init__(...) initializes x; see x.__class__.__doc__ for signature
    Callable:       Yes
    Call def:       Calling definition not available.Call docstring:
        x.__call__(...) <==> x(...)
    

A general description of Python:

  • A scripting language -- Python is suitable (1) for embedding, (2) for writing small unstructured scripts, (3) for "quick and dirty" programs.
  • Not a scripting language -- (1) Python scales. (2) Python encourages us to write code that is clear and well-structured.
  • Interpreted, but also compiled to byte-code. Modules are automatically compiled (to .pyc) when imported, but may also be explicitly compiled.
  • Provides an interactive command line and interpreter shell. In fact, there are several.
  • Dynamic -- For example:
    • Types are bound to values, not to variables.
    • Function and method lookup is done at runtime.
    • Values are inspect-able.
    • There is an interactive interpreter, more than one, in fact.
    • You can list the methods supported by any given object.
  • Strongly typed at run-time, not compile-time. Objects (values) have a type, but variables do not.
  • Reasonably high level -- High level built-in data types; high level control structures (for walking lists and iterators, for example).
  • Object-oriented -- Almost everything is an object. Simple object definition. Data hiding by agreement. Multiple inheritance. Interfaces by convention. Polymorphism.
  • Highly structured -- Statements, functions, classes, modules, and packages enable us to write large, well-structured applications. Why structure? Readability, locate-ability, modifiability.
  • Explicitness
  • First-class objects:
    • Definition: Can (1) pass to function; (2) return from function; (3) stuff into a data structure.
    • Operators can be applied to values (not variables). Example: f(x)[3]
  • Indented block structure -- "Python is pseudo-code that runs."
  • Embedding and extending Python -- Python provides a well-documented and supported way (1) to embed the Python interpreter in C/C++ applications and (2) to extend Python with modules and objects implemented in C/C++.
  • Automatic garbage collection.
  • Comparison with other languages: compiled languages (e.g. C/C++); Java; Perl and Tcl. Development speed. Execution speed. Clarity and maintainability.
  • Also see The Zen of Python -- http://www.python.org/peps/pep-0020.html

2   Lexical matters

2.1   Lines

  • Python does what you want it to do most of the time so that you only have to add extra characters some of the time.
  • Statement separator is a semi-colon, but is only needed when there is more than one statement on a line.
  • Continuation lines -- Use a back-slash at the end of the line. But, note that an opening bracket (or parenthesis) make the back-slash unnecessary.
  • Comments -- Everything after "#" on a line is ignored. No block comments, but doc strings are a comment in quotes at the beginning of a module, class, method or function. Also, editors with support for Python will comment out selected blocks of code, usually with "##".

2.2   Names and tokens

  • Allowed characters: a-z A-Z 0-9 underscore, and must begin with a letter or underscore.
  • Names and identifiers are case sensitive.
  • Identifiers can be of unlimited length.
  • Special names, customizing, etc. -- Usually begin and end in double underscores.
  • Special name classes -- Single and double underscores.
    • Leading double underscores -- Name mangling for method names.
    • Leading single underscore -- Suggests a "private" method name. Not imported by "from module import *".
  • Naming conventions -- Not rigid, but:
    • Modules and packages -- all lower case.
    • Globals and constants -- Upper case.
    • Classes -- Bumpy caps with initial upper.
    • Methods and functions -- All lower case with words separated by underscores.
    • Local variables -- Lower case (with underscore between words) or bumpy caps with initial lower or your choice.
  • Names/variables in Python do not have a type. Values have types.

2.3   Blocks and indentation

Python represents block structure and nested block structure with indentation, not with begin and end brackets.

The empty block -- Use the pass no-op statement.

Benefits of the use of indentation to indicate structure:

  • Reduces the need for a coding standard. Only need to specify that indentation is 4 spaces and no hard tabs.
  • Reduces inconsistency. Code from different sources follow the same indentation style. It has to.
  • Reduces work. Only need to get the indentation correct, not both indentation and brackets.
  • Reduces clutter. Eliminates all the curly brackets.
  • If it looks correct, it is correct. Indentation cannot fool the reader.

Editor considerations -- The standard is 4 spaces (no tabs) for each indentation level. You will need a text editor that helps you respect that.

2.4   Doc strings

Doc strings are like comments, but they are carried with executing code. Doc strings can be viewed with several tools, e.g. help(), obj.__doc__, and, in IPython, a question mark (?) after a name will produce help.

We can use triple-quoting to create doc strings that span multiple lines.

There are also tools that extract and format doc strings, for example:

2.5   Program structure

  • Statements, data structures, functions, classes, modules, packages.
  • Execution -- def, class, etc are executable statements that add something to the current name-space. Modules can be both executable and import-able.

2.6   Operators

  • See: http://docs.python.org/ref/operators.html. Python defines the following operators:

    +       -       *       **      /       //      %
    <<      >>      &       |       ^       ~
    <       >       <=      >=      ==      !=      <>
    

    The comparison operators <> and != are alternate spellings of the same operator. != is the preferred spelling; <> is obsolescent.

  • Logical operators:

    and     or      is      not     in
    
  • There are also (1) the dot operator, (2) the subscript operator [], and the function/method call operator ().

  • For information on the precedences of operators, see Summary of operators -- http://docs.python.org/ref/summary.html, which is reproduced below.

    The following table summarizes the operator precedences in Python, from lowest precedence (least binding) to highest precedence (most binding). Operators on the same line have the same precedence. Unless the syntax is explicitly given, operators are binary. Operators on the same line group left to right (except for comparisons, including tests, which all have the same precedence and chain from left to right -- see section 5.9 -- and exponentiation, which groups from right to left):

    Operator                  Description
    ========================  ==================
    lambda                    Lambda expression
    or                        Boolean OR
    and                       Boolean AND
    not x                     Boolean NOT
    in, not in                Membership tests
    is, is not                Identity tests
    <, <=, >, >=, <>, !=, ==  Comparisons
    |                         Bitwise OR
    ^                         Bitwise XOR
    &                         Bitwise AND
    <<, >>                    Shifts
    +, -                      Addition and subtraction
    *, /, %                   Multiplication, division, remainder
    +x, -x                    Positive, negative
    ~x                        Bitwise not
    **                        Exponentiation
    x.attribute               Attribute reference
    x[index]                  Subscription
    x[index:index]            Slicing
    f(arguments...)           Function call
    (expressions...)          Binding or tuple display
    [expressions...]          List display
    {key:datum...}            Dictionary display
    `expressions...`          String conversion
    
  • Note that most operators result in calls to methods with special names, for example __add__, __sub__, __mul__, etc. See Special method names http://docs.python.org/ref/specialnames.html

    Later, we will see how these operators can be emulated in classes that you define yourself, through the use of these special names.

2.8   Code evaluation

Understanding the Python execution model -- How Python evaluates and executes your code.

Evaluating expressions.

Creating names/variables -- Binding -- The following all create names (variables) and bind values (objects) to them: (1) assignment, (2) function definition, (3) class definition, (4) function and method call, (5) importing a module, ...

First class objects -- Almost all objects in Python are first class. Definition: An object is first class if: (1) we can put it in a structured object; (2) we can pass it to a function; (3) we can return it from a function.

References -- Objects (or references to them) can be shared. What does this mean?

  • The object(s) satisfy the identity test operator is.
  • The built-in function id() returns the same value.
  • The consequences for mutable objects are different from those for immutable objects.
  • Changing (updating) a mutable object referenced through one variable or container also changes that object referenced through other variables or containers, because it is the same object.
  • del() -- The built-in function del() removes a reference, not (necessarily) the object itself.

3   Built-in data-types

For information on built-in data types, see section Built-in Types -- http://docs.python.org/lib/types.html in the Python standard documentation.

3.1   Numeric types

The numeric types are:

  • Plain integers -- Same precision as a C long, usually a 32-bit binary number.
  • Long integers -- Define with 100L. But, plain integers are automatically promoted when needed.
  • Floats -- Implemented as a C double. Precision depends on your machine.
  • Complex numbers -- Define with, for example, 3j or complex(3.0, 2.0).

See 2.3.4 Numeric Types -- int, float, long, complex -- http://docs.python.org/lib/typesnumeric.html.

Python does mixed arithmetic.

Integer division truncates.

Scientific and heavily numeric programming -- High level Python and Jython are not very efficient for numerical programming. But, there are libraries that help:

3.2   Tuples and lists

List -- A list is a dynamic array/sequence. A list is mutable.

List constructors: [], list().

Tuples -- A tuple is a sequence. A tuple is immutable.

Tuple constructors: (), but really a comma and tuple().

Tuples are like lists, but are not mutable.

Python lists are (1) heterogeneous (2) indexable, and (3) dynamic (i.e. we can add to a list and make it longer).

Notes on sequence constructors:

  • To construct a tuple with a single element, use (x,); a tuple with a single element requires a comma.
  • You can spread elements across multiple lines (and no need for backslash continuation character "\").
  • A comma can follow the last element.

The length of a tuple or list (or other container): len(mylist).

Subscription:

  • Indexing into a sequence
  • Negative indexes -- Effectively, length of sequence plus (minus) index.
  • Slicing -- Example: data[2:5]. Default values: beginning and end of list.
  • Slicing with strides -- Example: data[::2].

Operations on tuples -- No operations that change the tuple, since tuples are immutable. We can do iteration and subscription. We can do "contains" (the in operator) and get the length (the len() operator). We can use certain boolean operators.

Operations on lists -- Operations similar to tuples plus:

  • Append -- mylist.append(newitem).
  • Insert -- mylist.insert(index, newitem).
  • Extend -- mylist.extend(anotherlist). Also can use + and +=.
  • Remove -- mylist.remove(item) and mylist.pop(). Note that append() together with pop() implements a stack.
  • Delete -- del mylist[index].
  • Pop -- Get last (right-most) item and remove from list -- mylist.pop().

List operators -- +, *, etc.

For more operations and operators on sequences, see: http://docs.python.org/lib/typesseq.html.

Exercises:

  • Create an empty list. Append 4 strings to the list. Then pop one item off the end of the list. Solution:

    In [25]: a = []
    In [26]: a.append('aaa')
    In [27]: a.append('bbb')
    In [28]: a.append('ccc')
    In [29]: a.append('ddd')
    In [30]: print a
    ['aaa', 'bbb', 'ccc', 'ddd']
    In [31]: a.pop()
    Out[31]: 'ddd'
    
  • Use the for statement to print the items in the list. Solution:

    In [32]: for item in a:
       ....:         print item
       ....:
    aaa
    bbb
    ccc
    
  • Use the string join operation to concatenate the items in the list. Solution:

    In [33]: '||'.join(a)
    Out[33]: 'aaa||bbb||ccc'
    
  • Use lists containing three (3) elements to create and show a tree:

    In [37]: b = ['bb', None, None]
    In [38]: c = ['cc', None, None]
    In [39]: root = ['aa', b, c]
    In [40]:
    In [40]:
    In [40]: def show_tree(t):
       ....:     if not t:
       ....:         return
       ....:     print t[0]
       ....:     show_tree(t[1])
       ....:     show_tree(t[2])
       ....:
       ....:
    In [41]: show_tree(root)
    aa
    bb
    cc
    

    Note that we will learn a better way to represent tree structures when we cover implementing classes in Python.

3.3   Strings

Strings are sequences. They are immutable. They are indexable. They are iterable.

For operations on strings, see http://docs.python.org/lib/string-methods.html or use:

>>> help(str)

Or:

>>> dir("abc")

String operations (methods).

String operators, e.g. +, <, <=, ==, etc..

Constructors/literals:

  • Quotes: single and double. Escaping quotes and other special characters with a back-slash.
  • Triple quoting -- Use triple single quotes or double quotes to define multi-line strings.
  • str() -- The constructor and the name of the type/class.
  • 'aSeparator'.join(aList)
  • Many more.

Escape characters in strings -- \t, \n, \\, etc.

String formatting -- See: 2.3.6.2 String Formatting Operations -- http://docs.python.org/lib/typesseq-strings.html. Examples:

In [18]: name = 'dave'
In [19]: size = 25
In [20]: factor = 3.45
In [21]: print 'Name: %s  Size: %d  Factor: %3.4f' % (name, size, factor, )
Name: dave  Size: 25  Factor: 3.4500
In [25]: print 'Name: %s  Size: %d  Factor: %08.4f' % (name, size, factor, )
Name: dave  Size: 25  Factor: 003.4500

If the right-hand argument to the formatting operator is a dictionary, then you can (actually, must) use the names of keys in the dictionary in your format strings. Examples:

In [115]: values = {'vegetable': 'chard', 'fruit': 'nectarine'}
In [116]: 'I love %(vegetable)s and I love %(fruit)s.' % values
Out[116]: 'I love chard and I love nectarine.'

Also consider using the right justify and left justify operations. Examples: mystring.rjust(20), mystring.ljust(20, ':').

Exercises:

  • Use a literal to create a string containing (1) a single quote, (2) a double quote, (3) both a single and double quote. Solutions:

    "Some 'quoted' text."
    'Some "quoted" text.'
    'Some "quoted" \'extra\' text.'
    
  • Write a string literal that spans multiple lines. Solution:

    """This string
    spans several lines
    because it is a little long.
    """
    
  • Use the string join operation to create a string that contains a colon as a separator. Solution:

    >>> content = []
    >>> content.append('finch')
    >>> content.append('sparrow')
    >>> content.append('thrush')
    >>> content.append('jay')
    >>> contentstr = ':'.join(content)
    >>> print contentstr
    finch:sparrow:thrush:jay
    
  • Use string formatting to produce a string containing your last and first names, separated by a comma. Solution:

    >>> first = 'Dave'
    >>> last = 'Kuhlman'
    >>> full = '%s, %s' % (last, first, )
    >>> print full
    Kuhlman, Dave
    

Incrementally building up large strings from lots of small strings -- Since strings in Python are immutable, appending to a string requires a re-allocation. So, it is faster to append to a list, then use join. Example:

In [25]: strlist = []
In [26]: strlist.append('Line #1')
In [27]: strlist.append('Line #2')
In [28]: strlist.append('Line #3')
In [29]: str = '\n'.join(strlist)
In [30]: print str
Line #1
Line #2
Line #3

3.3.1   Unicode strings

Representing unicode:

In [96]: a = u'abcd'
In [97]: a
Out[97]: u'abcd'
In [98]: b = unicode('efgh')
In [99]: b
Out[99]: u'efgh'

Convert to unicode: a_string.decode(encoding). Examples:

In [102]: 'abcd'.decode('utf-8')
Out[102]: u'abcd'
In [103]:
In [104]: 'abcd'.decode(sys.getdefaultencoding())
Out[104]: u'abcd'

Convert out of unicode: a_unicode_string.encode(encoding). Examples:

In [107]: a = u'abcd'
In [108]: a.encode('utf-8')
Out[108]: 'abcd'
In [109]: a.encode(sys.getdefaultencoding())
Out[109]: 'abcd'

Test for unicode type -- Example:

In [122]: import types
In [123]: a = u'abcd'
In [124]: type(a) is types.UnicodeType
Out[124]: True
In [125]:
In [126]: type(a) is type(u'')
Out[126]: True

An example with a character "c" with a hachek:

In [135]: name = 'Ivan Krsti\xc4\x87'
In [136]: name.decode('utf-8')
Out[136]: u'Ivan Krsti\u0107'
In [137]:
In [138]: len(name)
Out[138]: 12
In [139]: len(name.decode('utf-8'))
Out[139]: 11

You can also create a unicode character by using the unichr() builtin function:

In [2]: a = 'aa' + unichr(170) + 'bb'
In [3]: a
Out[3]: u'aa\xaabb'
In [6]: b = a.encode('utf-8')
In [7]: b
Out[7]: 'aa\xc2\xaabb'
In [8]: print b
aaªbb

Guidance for use of encodings and unicode:

  1. Convert/decode from an external encoding to unicode early (my_string.decode(encoding)).
  2. Do your work in unicode.
  3. Convert/encode to an external encoding late (my_string.encode(encoding)).

For more information, see:

3.4   Dictionaries

A dictionary is a collection, whose values are accessible by key. It is a collection of name-value pairs.

The order of elements in a dictionary is undefined. But, we can iterate over (1) the keys, (2) the values, and (3) the items (key-value pairs) in a dictionary. We can set the value of a key and we can get the value associated with a key.

Keys must be immutable objects: ints, strings, tuples, ...

Literals for constructing dictionaries:

d1 = {}
d2 = {key1: value1, key2: value2, }

Constructor for dictionaries -- dict() is a factory function that constructs dictionaries. Some examples, which were taken from 2.1 Built-in Functions -- http://docs.python.org/lib/built-in-funcs.html.

dict({'one': 2, 'two': 3})
dict({'one': 2, 'two': 3}.items())
dict({'one': 2, 'two': 3}.iteritems())
dict(zip(('one', 'two'), (2, 3)))
dict([['two', 3], ['one', 2]])
dict(one=2, two=3)
dict([(['one', 'two'][i-2], i) for i in (2, 3)])

For operations on dictionaries, see http://docs.python.org/lib/typesmapping.html or use:

>>> help({})

Or:

>>> dir({})

Indexing -- Access or add items to a dictionary with the indexing operator [ ]. Example:

In [102]: dict1 = {}
In [103]: dict1['name'] = 'dave'
In [104]: dict1['category'] = 38
In [105]: dict1
Out[105]: {'category': 38, 'name': 'dave'}

Some of the operations produce the keys, the values, and the items (pairs) in a dictionary. Examples:

In [43]: d = {'aa': 111, 'bb': 222}
In [44]: d.keys()
Out[44]: ['aa', 'bb']
In [45]: d.values()
Out[45]: [111, 222]
In [46]: d.items()
Out[46]: [('aa', 111), ('bb', 222)]

For large dictionaries, use methods iterkeys(), itervalues(), and iteritems(). Example:

In [47]:
In [47]: d = {'aa': 111, 'bb': 222}
In [48]: for key in d.iterkeys():
   ....:     print key
   ....:
   ....:
aa
bb

To test for the existence of a key in a dictionary, use the in operator or the mydict.has_key(k) method. The in operator is preferred. Example:

>>> d = {'tomato': 101, 'cucumber': 102}
>>> k = 'tomato'
>>> k in d
True
>>> d.has_key(k)
True

You can often avoid the need for a test by using method get. Example:

>>> d = {'tomato': 101, 'cucumber': 102}
>>> d.get('tomato', -1)
101
>>> d.get('chard', -1)
-1
>>> if d.get('eggplant') is None:
...     print 'missing'
...
missing

Exercises:

  • Write a literal that defines a dictionary using both string literals and variables containing strings. Solution:

    >>> first = 'Dave'
    >>> last = 'Kuhlman'
    >>> name_dict = {first: last, 'Elvis': 'Presley'}
    >>> print name_dict
    {'Dave': 'Kuhlman', 'Elvis': 'Presley'}
    
  • Write statements that iterate over (1) the keys, (2) the values, and (3) the items in a dictionary. (Note: Requires introduction of the for statement.) Solutions:

    >>> d = {'aa': 111, 'bb': 222, 'cc': 333}
    >>> for key in d.keys():
    ...   print key
    ...
    aa
    cc
    bb
    >>> for value in d.values():
    ...   print value
    ...
    111
    333
    222
    >>> for item in d.items():
    ...   print item
    ...
    ('aa', 111)
    ('cc', 333)
    ('bb', 222)
    >>> for key, value in d.items():
    ...   print key, '::', value
    ...
    aa :: 111
    cc :: 333
    bb :: 222
    

Additional notes on dictionaries:

  • You can use iterkeys(), itervalues(), iteritems() to obtain iterators over keys, values, and items.

  • A dictionary itself is iterable: it iterates over its keys. So, the following two lines are equivalent:

    for k in myDict: print k
    for k in myDict.iterkeys(): print k
    
  • The in operator tests for a key in a dictionary. Example:

    In [52]: mydict = {'peach': 'sweet', 'lemon': 'tangy'}
    In [53]: key = 'peach'
    In [54]: if key in mydict:
       ....:     print mydict[key]
       ....:
    sweet
    

3.5   Files

Open a file with the open factory method. Example:

In [28]: f = open('mylog.txt', 'w')
In [29]: f.write('message #1\n')
In [30]: f.write('message #2\n')
In [31]: f.write('message #3\n')
In [32]: f.close()
In [33]: f = file('mylog.txt', 'r')
In [34]: for line in f:
   ....:     print line,
   ....:
message #1
message #2
message #3
In [35]: f.close()

Notes:

  • Use the (built-in) open(path, mode) function to open a file and create a file object. You could also use file(), but open() is recommended.

  • A file object supports the iterator protocol and, therefore, can be used in a for statement. It iterates over the lines in the file. This is most likely only useful for text files.

  • open is a factory method that creates file objects. Use it to open files for reading, writing, and appending. Examples:

    infile = open('myfile.txt', 'r')    # open for reading
    outfile = open('myfile.txt', 'w')   # open for (over-) writing
    log = open('myfile.txt', 'a')       # open for appending to existing content
    
  • When you have finished with a file, close it. Examples:

    infile.close()
    outfile.close()
    
  • file is the file type and can be used as a constructor to create file objects. But, open is preferred.

  • Lines read from a text file have a newline. Strip it off with something like: line.rstrip('\n').

  • For binary files you should add the binary mode, for example: rb, wb. For more about modes, see the description of the open() function at Built-in Functions -- http://docs.python.org/lib/built-in-funcs.html.

  • Learn more about file objects and the methods they provide at: 2.3.9 File Objects -- http://docs.python.org/lib/bltin-file-objects.html.

You can also append to an existing file. Note the "a" mode in the following example:

In [39]: f = open('mylog.txt', 'a')
In [40]: f.write('message #4\n')
In [41]: f.close()
In [42]: f = file('mylog.txt', 'r')
In [43]: for line in f:
   ....:     print line,
   ....:
message #1
message #2
message #3
message #4
In [44]: f.close()

For binary files, add "b" to the mode. Not strictly necessary on UNIX, but needed on MS Windows. And, you will want to make your code portable across platforms. Example:

In [62]: import zipfile
In [63]: outfile = open('tmp1.zip', 'wb')
In [64]: zfile = zipfile.ZipFile(outfile, 'w', zipfile.ZIP_DEFLATED)
In [65]: zfile.writestr('entry1', 'my heroes have always been cowboys')
In [66]: zfile.writestr('entry2', 'and they still are it seems')
In [67]: zfile.writestr('entry3', 'sadly in search of and')
In [68]: zfile.writestr('entry4', 'on step in back of')
In [69]:
In [70]: zfile.writestr('entry4', 'one step in back of')
In [71]: zfile.writestr('entry5', 'themselves and their slow moving ways')
In [72]: zfile.close()
In [73]: outfile.close()
In [75]:
$
$ unzip -lv tmp1.zip
Archive:  tmp1.zip
 Length   Method    Size  Ratio   Date   Time   CRC-32    Name
--------  ------  ------- -----   ----   ----   ------    ----
      34  Defl:N       36  -6%  05-29-08 17:04  f6b7d921  entry1
      27  Defl:N       29  -7%  05-29-08 17:07  10da8f3d  entry2
      22  Defl:N       24  -9%  05-29-08 17:07  3fd17fda  entry3
      18  Defl:N       20 -11%  05-29-08 17:08  d55182e6  entry4
      19  Defl:N       21 -11%  05-29-08 17:08  1a892acd  entry4
      37  Defl:N       39  -5%  05-29-08 17:09  e213708c  entry5
--------          -------  ---                            -------
     157              169  -8%                            6 files

Exercises:

  • Read all of the lines of a file into a list. Print the 3rd and 5th lines in the file/list. Solution:

    In [55]: f = open('tmp1.txt', 'r')
    In [56]: lines = f.readlines()
    In [57]: f.close()
    In [58]: lines
    Out[58]: ['the\n', 'big\n', 'brown\n', 'dog\n', 'had\n', 'long\n', 'hair\n']
    In [59]: print lines[2]
    brown
    
    In [61]: print lines[4]
    had
    

More notes:

  • Strip newlines (and other whitespace) from a string with methods strip(), lstrip(), and rstrip().

3.6   Other built-in types

Other built-in data types are described in section Built-in Types -- http://docs.python.org/lib/types.html in the Python standard documentation.

3.6.1   The None value/type

The unique value None is used to indicate "no value", "nothing", "non-existence", etc. There is only one None value.

Use is to test for None. Example:

>>> flag = None
>>>
>>> if flag is None:
...     print 'clear'
...
clear
>>> if flag is not None:
...     print 'hello'
...
>>>

3.6.2   Boolean values

True and False are the boolean values.

The following values also count as false: False, numeric 0, None, the empty string, an empty list, an empty dictionary, any empty container, etc. All other values, including True, act as true values.

Jython has True and False, but their values are int 1 and 0, as of Jython 2.2.1.

3.6.3   Sets and frozensets

A set is an unordered collection of immutable objects. A set does not contain duplicates.

Sets support several set operations, for example: union, intersection, difference, ...

A frozenset is like a set, except that a frozenset is immutable. Therefore, a frozenset is hash-able and can be used as a key in a dictionary.

Create a set with the set constructor. Examples:

>>> a = set()
>>> a
set([])
>>> a.add('aa')
>>> a.add('bb')
>>> a
set(['aa', 'bb'])
>>> b = set([11, 22])
>>> b
set([11, 22])
>>> c = set([22, 33])
>>> b.union(c)
set([33, 11, 22])
>>> b.intersection(c)
set([22])

For more information on sets, see: Set Types -- set, frozenset -- http://docs.python.org/lib/types-set.html

Jython 2.2.1 does not have sets. Consider using java.util.HashSet, java.util.LinkedHashSet, or java.util.TreeSet, each of which implement the java.util.Set interface. This Jython example uses class HashSet:

>>> # Jython, not Python
>>>
>>> from java.util import HashSet
>>> a = HashSet()
>>> a.add('aa')
1
>>> a.add('bb')
1
>>> a
[aa, bb]

4   Functions and Classes -- A Preview

Structured code -- Python programs are made up of expressions, statements, functions, classes, modules, and packages.

Python objects are first-class objects.

Expressions are evaluated.

Statements are executed.

Functions (1) are objects and (2) are callable.

Object-oriented programming in Python. Modeling "real world" objects. (1) Encapsulation; (2) data hiding; (3) inheritance. Polymorphism.

Classes -- (1) encapsulation; (2) data hiding; (3) inheritance.

An overview of the structure of a typical class: (1) methods; (2) the constructor; (3) class (static) variables; (4) super/sub-classes.

5   Statements

5.1   Assignment

Form -- target = expression.

Possible targets:

  • Identifier

  • Tuple or list -- Can be nested. Left and right sides must have equivalent structure. Example:

    >>> x, y, z = 11, 22, 33
    >>> [x, y, z] = 111, 222, 333
    >>> a, (b, c) = 11, (22, 33)
    >>> a, B = 11, (22, 33)
    

    This feature can be used to simulate an enum:

    In [22]: LITTLE, MEDIUM, LARGE = range(1, 4)
    In [23]: LITTLE
    Out[23]: 1
    In [24]: MEDIUM
    Out[24]: 2
    
  • Subscription of a sequence, dictionary, etc.

  • A slice of a sequence -- Note that the sequence must be mutable.

  • Attribute reference -- Example:

    >>> class MyClass:
    ...     pass
    ...
    >>> anObj = MyClass()
    >>> anObj.desc = 'pretty'
    >>> print anObj.desc
    pretty
    

There is also augmented assignment. Examples:

>>> index = 0
>>> index += 1
>>> index += 5
>>> index += f(x)
>>> index -= 1
>>> index *= 3

Things to note:

  • Assignment to a name creates a new variable (if it does not exist in the namespace) and a binding. Specifically, it binds a value to the new name. Calling a function also does this to the (formal) parameters.

  • In Python, a language with dynamic typing, the data type is associated with the value, not the variable, as in statically typed languages.

  • Assignment can also cause sharing of an object. Example:

    obj1 = A()
    obj2 = obj1
    

    Check to determine that the same object is shared with id(obj) or the is operator.

  • You can also do multiple assignment in a single statement. Example:

    a = b = 3
    
  • You can interchange (swap) the value of two variables using assignment and packing/unpacking:

    >>> a = 111
    >>> b = 222
    >>> a, b = b, a
    >>> a
    222
    >>> b
    111
    

5.2   import

Make module (or objects in the module) available.

What import does:

  • Evaluate the content of a module.

  • Likely to create variables in the local (module) namespace.

  • Evaluation of a specific module only happens once during a given run of the program. Therefore, a module is shared across an application.

  • A module is evaluated from top to bottom. Later statements can replace values created earlier. This is true of functions and classes, as well as (other) variables.

  • Which statements are evaluated? Assignment, class, def, ...

  • Use the following idiom to make a module both run-able and import-able:

    if __name__ == '__main__':
        # import pdb; pdb.set_trace()
        main()        # or "test()" or some other function defined in module
    

    Notes:

    • The above condition will be true only when the module is run as a script and will not be true when the module is imported.
    • The line containing pdb can be copied any place in your program and un-commented, and then the program will drop into the Python debugger when that location is reached.

Where import looks for modules:

  • sys.path shows where it looks.

  • There are some standard places.

  • Add additional directories by setting the environment variable PYTHONPATH.

  • You can also add paths by modifying sys.path, for example:

    import sys
    sys.path.insert(0, '/path/to/my/module')
    
  • Packages need a file named __init__.py.

  • Extensions -- To determine what extensions import looks for, do:

    >>> import imp
    >>> imp.get_suffixes()
    [('.so', 'rb', 3), ('module.so', 'rb', 3), ('.py', 'U', 1), ('.pyc', 'rb', 2)]
    

Forms of the import statement:

  • import A -- Names in the local (module) namespace are accessible with the dot operator.
  • import A as B -- Import the module A, but bind the module object to the variable B.
  • import A1, A2 -- Not recommended
  • from A import B
  • from A import B1, B2
  • from A import B as C
  • from A import * -- Not recommended: clutters and mixes name-spaces.

More notes on the import statement:

  • The import statement and packages -- A file named __init__.py is required in a package. This file is evaluated the first time either the package is imported or a file in the package is imported. Question: What is made available when you do import aPackage? Answer: All variables (names) that are global inside the __init__.py module in that package. But, see notes on the use of __all__: The import statement -- http://docs.python.org/ref/import.html

  • The use of if __name__ == "__main__": -- Makes a module both import-able and executable.

  • Using dots in the import statement -- From the Python language reference manual:

    "Hierarchical module names:when the module names contains one or more dots, the module search path is carried out differently. The sequence of identifiers up to the last dot is used to find a package; the final identifier is then searched inside the package. A package is generally a subdirectory of a directory on sys.path that has a file __init__.py."

    See: The import statement -- http://docs.python.org/ref/import.html

Exercises:

  • Import a module from the standard library, for example re.

  • Import an element from a module from the standard library, for example import compile from the re module.

  • Create a simple Python package with a single module in it. Solution:

    1. Create a directory named simplepackage in the current directory.

    2. Create an (empty) __init__.py in the new directory.

    3. Create an simple.py in the new directory.

    4. Add a simple function name test1 in simple.py.

    5. Import using any of the following:

      >>> import simplepackage.simple
      >>> from simplepackage import simple
      >>> from simplepackage.simple import test1
      >>> from simplepackage.simple import test1 as mytest
      

5.3   print

print sends output to sys.stdout. It adds a newline, unless an extra comma is added.

Arguments to print:

String formatting -- Arguments are a tuple. Reference: 2.3.6.2 String Formatting Operations -- http://docs.python.org/lib/typesseq-strings.html.

Can also use sys.stdout. Note that a carriage return is not automatically added. Example:

>>> import sys
>>> sys.stdout.write('hello\n')

Controlling the destination and format of print -- Replace sys.stdout with an instance of any class that implements the method write taking one parameter. Example:

import sys

class Writer:
    def __init__(self, file_name):
        self.out_file = file(file_name, 'a')
    def write(self, msg):
        self.out_file.write('[[%s]]' % msg)
    def close(self):
        self.out_file.close()

def test():
    writer = Writer('outputfile.txt')
    save_stdout = sys.stdout
    sys.stdout = writer
    print 'hello'
    print 'goodbye'
    writer.close()
    # Show the output.
    tmp_file = file('outputfile.txt')
    sys.stdout = save_stdout
    content = tmp_file.read()
    tmp_file.close()
    print content

test()

There is an alternative form of the print statement that takes a file-like object, in particular an object that has a write method. For example:

In [1]: outfile = open('tmp.log', 'w')
In [2]: print >> outfile, 'Message #1'
In [3]: print >> outfile, 'Message #2'
In [4]: print >> outfile, 'Message #3'
In [5]: outfile.close()
In [6]:
In [6]: infile = open('tmp.log', 'r')
In [7]: for line in infile:
   ...:     print 'Line:', line.rstrip('\n')
   ...:
Line: Message #1
Line: Message #2
Line: Message #3
In [8]: infile.close()

5.4   if: elif: else:

A template for the if: statement:

if condition1:
    statements
elif condition2:
    statements
elif condition3:
    statements
else:
    statements

Conditions -- Expressions -- Anything that returns a value. Compare with eval() and exec.

Truth values:

  • False -- False, None, numeric zero, the empty string, an empty collection (list or tuple or dictionary or ...).
  • True -- True and everything else.

Operators:

  • and and or

  • not

  • is and is not -- The identical object. Cf. a is b and id(a) == id(b). Useful to test for None, for example:

    if x is None:
        ...
    if x is not None:
        ...
    
  • in and not in -- Can be used to test for existence of a key in a dictionary or for the presence of a value in a collection.

    The in operator tests for equality, not identity.

    Example:

    >>> d = {'aa': 111, 'bb': 222}
    >>> 'aa' in d
    True
    >>> 'aa' not in d
    False
    >>> 'xx' in d
    False
    
  • Comparison operators, for example ==, !=, <, <=, ...

  • An if expression. Example:

    >>> a = 'aa'
    >>> b = 'bb'
    >>> x = 'yes' if a == b else 'no'
    >>> x
    'no'
    

Notes:

  • The elif: clauses and the else: clause are optional.
  • The if:, elif:, and else: clauses are all header lines in the sense that they are each followed by an indented block of code and each of these lines ends with a colon.

Exercises:

  • Write an if statement with an and operator.
  • Write an if statement with an or operator.
  • Write an if statement containing both and and or operators.

5.5   for:

Iterate over a sequence or an "iterable" object.

Form:

for x in y:
    block

Iterator -- Some notes on what it means to be iterable:

Some ways to produce iterators (see http://docs.python.org/lib/built-in-funcs.html):

  • iter()

  • enumerate()

  • some_dict.iterkeys(), some_dict.itervalues(), some_dict.iteritems().

  • Use a sequence in an iterator context, for example in a for statement. Lists, tuples, dictionaries, and strings can be used in an iterator context to produce an iterator.

  • Generator expressions -- Latest Python only. Syntactically like list comprehensions (surrounded by parentheses instead of square brackets), but use lazy evaluation.

  • A class that implements the iterator protocol -- Example:

    class A:
        def __init__(self):
            self.data = [11,22,33]
            self.idx = 0
        def __iter__(self):
            return self
        def next(self):
            if self.idx < len(self.data):
                x = self.data[self.idx]
                self.idx +=1
                return x
            else:
                raise StopIteration
    
    def test():
        a = A()
        for x in a:
            print x
    
    test()
    
  • A function containing a yield statement. See:

The for statement can also have an optional else: clause. The else: clause is executed if the for statement completes normally, that is if a break statement is not executed.

Helpful functions with for:

  • enumerate(iterable) -- Returns an iterable that produces pairs (tuples) containing count and value. Example:

    >>> for idx, value in enumerate([11,22,33]):
    ...     print idx, value
    ...
    0 11
    1 22
    2 33
    
  • range([start,] stop[, step]) and xrange([start,] stop[, step]).

List comprehensions revisited -- Since list comprehensions create lists, they are useful in for statements, although you should consider using a generator expression instead. Two forms:

  • [f(x) for x in iterable]
  • [f(x) for x in iterable if t(x)]

Exercises:

  • Write a list comprehension that returns all the keys in a dictionary whose associated values are greater than zero.

    • The dictionary: {'aa': 11, 'cc': 33, 'dd': -55, 'bb': 22}
    • Solution: [x[0] for x in my_dict.iteritems() if x[1] > 0]
  • Write a list comprehension that produces even integers from 0 to 10. Use a for statement to iterate over those values. Solution:

    for x in [y for y in range(10) if y % 2 == 0]:
        print 'x: %s' % x
    

But, note that in the previous exercise, a generator expression would be better. A generator expression is like a list comprehension, except that, instead of creating the entire list, it produces a generator that can be used to produce all the elements.

The break and continue statements are often useful in a for statement.

The for statement can also have an optional else: clause. The else: clause is executed if the for statement completes normally, that is if a break statement is not executed.

5.6   while:

Form:

while condition:
    block

Exercises:

  • Write a while statement that prints integers from zero to 5. Solution:

    count = 0
    while count < 5:
        count += 1
        print count
    

The break and continue statements are often useful in a while statement.

The while statement can also have an optional else: clause. The else: clause is executed if the while statement completes normally, that is if a break statement is not executed.

5.7   continue and break

The break statement exits from a loop.

The continue statement causes execution to immediately continue at the start of the loop.

Can be used in for: and while:.

Exercises:

  • Using break, write a while statement that prints integers from zero to 5. Solution:

    count = 0
    while True:
        count += 1
        if count > 5:
            break
        print count
    
  • Using continue, write a while statement that processes only even integers from 0 to 10. Note: % is the modulo operator. Solution:

    count = 0
    while count < 10:
        count += 1
        if count % 2 == 0:
            continue
        print count
    

5.8   try: except:

Exceptions are a systematic and consistent way of processing errors and "unusual" events in Python.

Caught and un-caught exceptions -- Uncaught exceptions terminate a program.

The try: statement catches an exception.

Almost all errors in Python are exceptions.

Evaluation (execution model) of the try statement -- When an exception occurs in the try block, even if inside a nested function call, execution of the try block ends and the except clauses are searched for a matching exception.

Tracebacks -- Also see the traceback module: http://docs.python.org/lib/module-traceback.html

Exceptions are classes.

Exception classes -- Sub-classing, args.

An exception class in an except: clause catches instances of that exception class and all sub-classes, but not super-classes.

Built-in exception classes -- See:

User defined exception classes -- Sub-classes of Exception.

Example:

try:
    raise RuntimeError, 'this silly error'
except RuntimeError, e:
    print "[[[%s]]]" % e

Reference: http://docs.python.org/lib/module-exceptions.html

You can also get the arguments passed to the constructor of an exception object. In the above example, these would be:

e.args

Why would you define your own exception class? One answer: You want a user of your code to catch your exception and no others.

Catching an exception by exception class catches exceptions of that class and all its subclasses. So:

except SomeExceptionClass, e:

matches and catches an exception if SomeExceptionClass is the exception class or a base class (superclass) of the exception class. So:

class MyE(ValueError):
    pass

try:
    raise MyE
except ValueError:
    print 'caught exception'

will print "caught exception", because ValueError is a base class of MyE.

Exercises:

  • Write a very simple, empty exception sub-class. Solution:

    class MyE(Exception):
        pass
    
  • Write a try:except: statement that raises your exception and also catches it. Solution:

    try:
        raise MyE, 'hello there dave'
    except MyE, e:
        print e
    

5.9   raise

Throw or raise an exception.

Forms:

  • raise instance
  • raise MyExceptionClass(value)
  • raise MyExceptionClass, value -- preferred.

The raise statement takes:

  • An (instance of) a built-in exception class. See:
  • An instance of class Exception or
  • An instance of a built-in sub-class of class Exception or
  • An instance of a user-defined sub-class of class Exception or
  • One of the above classes and (optionally) a value (for example, a string or a tuple).

See http://docs.python.org/ref/raise.html.

For a list of built-in exceptions, see http://docs.python.org/lib/module-exceptions.html.

The following example defines an exception sub-class and throws an instance of that sub-class. It also shows how to pass and catch multiple arguments to the exception:

class NotsobadError(Exception):
    pass

def test(x):
    try:
        if x == 0:
            raise NotsobadError, ('a moderately bad error', 'not too bad')
    except NotsobadError, e:
        print 'Error args: %s' % (e.args, )

test(0)

Which prints out the following:

Error args: ('a moderately bad error', 'not too bad')

Notes:

  • In order to pass in multiple arguments with the exception, we use a tuple.

The following example does a small amount of processing of the arguments:

class NotsobadError(Exception):
    """An exception class.
    """
    def get_args(self):
        return '::::'.join(self.args)

def test(x):
    try:
        if x == 0:
            raise NotsobadError, ('a moderately bad error', 'not too bad')
    except NotsobadError, e:
        print 'Error args: {{{%s}}}' % (e.get_args(), )

test(0)

5.10   del

The del statement can be used to:

  • Remove names from namespace.
  • Remove items from a collection.

If name is listed in a global statement, then del removes name from the global namespace.

Names can be a (nested) list. Examples:

>>> del a
>>> del a, b, c

We can also delete items of a list or dictionary (and perhaps from other objects that we can subscript). Examples:

In [9]:d = {'aa': 111, 'bb': 222, 'cc': 333}
In [10]:print d
{'aa': 111, 'cc': 333, 'bb': 222}
In [11]:del d['bb']
In [12]:print d
{'aa': 111, 'cc': 333}
In [13]:
In [13]:a = [111, 222, 333, 444]
In [14]:print a
[111, 222, 333, 444]
In [15]:del a[1]
In [16]:print a
[111, 333, 444]

And, we can delete an attribute from an instance. Example:

In [17]:class A:
   ....:    pass
   ....:
In [18]:a = A()
In [19]:a.x = 123
In [20]:dir(a)
Out[20]:['__doc__', '__module__', 'x']
In [21]:print a.x
123
In [22]:del a.x
In [23]:dir(a)
Out[23]:['__doc__', '__module__']
In [24]:print a.x
----------------------------------------------
exceptions.AttributeError     Traceback (most recent call last)

/home/dkuhlman/a1/Python/Test/<console>

AttributeError: A instance has no attribute 'x'

6   Functions, Modules, Packages, and Debugging

6.1   Functions

6.1.1   The def statement

The def statement is used to define functions and methods.

The def statement is evaluated. It produces a function/method (object) and binds it to a variable in the current name-space.

6.1.2   Returning values

The return statement is used to return values from a function.

The return statement takes zero or more values, separated by commas. Using commas actually returns a single tuple.

The default value is None.

To return multiple values, use a tuple or list. Don't forget that (assignment) unpacking can be used to capture multiple values. Returning multiple items separated by commas is equivalent to returning a tuple. Example:

In [8]: def test(x, y):
   ...:     return x * 3, y * 4
   ...:
In [9]: a, b = test(3, 4)
In [10]: print a
9
In [11]: print b
16

6.1.3   Parameters

Default values -- Example:

In [53]: def t(max=5):
   ....:     for val in range(max):
   ....:         print val
   ....:
   ....:
In [54]: t(3)
0
1
2
In [55]: t()
0
1
2
3
4

Note: If a function has a parameter with a default value, then all "normal" arguments must proceed the parameters with default values. More completely, parameters must be given from left to right in the following order:

  1. Normal arguments.
  2. Arguments with default values.
  3. Argument list (*args).
  4. Keyword arguments (**kwargs).

List parameters -- *args. It's a tuple.

Keyword parameters -- **kwargs. It's a dictionary.

6.1.4   Arguments

When calling a function, values may be passed to a function with positional arguments or keyword arguments.

Positional arguments must placed before (to the left of) keyword arguments.

Passing lists to a function as multiple arguments -- some_func(*aList). Effectively, this syntax causes Python to unroll the arguments.

6.1.5   Local variables

Creating local variables -- Any binding operation creates a local variable. Examples are (1) parameters of a function; (2) assignment to a variable in a function; (3) the import statement; (4) etc. Contrast with accessing a variable.

Variable look-up -- The LGB/LEGB rule.

The global statement -- Must use global when we want to set the value of a global variable.

6.1.6   Other things to know about functions

  • Functions are first-class -- You can store them in a structure, pass them to a function, and return them from a function.

  • Function calls can take keyword arguments. Example:

    >>> test(size=25)
    
  • Formal parameters to a function can have default values. Example:

    >>> def test(size=0):
            ...
    

    Do not use mutable objects as default values.

  • You can "capture" remaining arguments with *args, and **kwargs. (Spelling is not significant.) Example:

    In [13]: def test(size, *args, **kwargs):
       ....:     print size
       ....:     print args
       ....:     print kwargs
       ....:
       ....:
    In [14]: test(32, 'aa', 'bb', otherparam='xyz')
    32
    ('aa', 'bb')
    {'otherparam': 'xyz'}
    
  • Normal arguments must come before default arguments which must come before keyword arguments.

  • A function that does not explicitly return a value, returns None.

  • In order to set the value of a global variable, declare the variable with global.

Exercises:

  • Write a function that takes a single argument, prints the value of the argument, and returns the argument as a string. Solution:

    >>> def t(x):
    ...     print 'x: %s' % x
    ...     return '[[%s]]' % x
    ...
    >>> t(3)
    x: 3
    '[[3]]'
    
  • Write a function that takes a variable number of arguments and prints them all. Solution:

    >>> def t(*args):
    ...     for arg in args:
    ...         print 'arg: %s' % arg
    ...
    >>> t('aa', 'bb', 'cc')
    arg: aa
    arg: bb
    arg: cc
    
  • Write a function that prints the names and values of keyword arguments passed to it. Solution:

    >>> def t(**kwargs):
    ...     for key in kwargs.keys():
    ...         print 'key: %s  value: %s' % (key, kwargs[key], )
    ...
    >>> t(arg1=11, arg2=22)
    key: arg1  value: 11
    key: arg2  value: 22
    

6.1.7   Global variables and the global statement

By default, assignment in a function or method creates local variables.

Reference (not assignment) to a variable, accesses a local variable if it has already been created, else accesses a global variable.

In order to assign a value to a global variable, declare the variable as global at the beginning of the function or method.

If in a function or method, you both reference and assign to a variable, then you must either:

  1. Assign to the variable first, or
  2. Declare the variable as global.

The global statement declares one or more variables, separated by commas, to be global.

Some examples:

In [1]:
In [1]: X = 3
In [2]: def t():
   ...:       print X
   ...:
In [3]:
In [3]: t()
3
In [4]: def s():
   ...:       X = 4
   ...:
In [5]:
In [5]:
In [5]: s()
In [6]: t()
3
In [7]: X = -1
In [8]: def u():
   ...:       global X
   ...:   X = 5
   ...:
In [9]:
In [9]: u()
In [10]: t()
5
In [16]: def v():
   ....:       x = X
   ....:   X = 6
   ....:   return x
   ....:
In [17]:
In [17]: v()
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
  File "<ipython console>", line 2, in v
UnboundLocalError: local variable 'X' referenced before assignment
In [18]: def w():
   ....:       global X
   ....:   x = X
   ....:   X = 7
   ....:   return x
   ....:
In [19]:
In [19]: w()
Out[19]: 5
In [20]: X
Out[20]: 7

6.1.8   Doc strings for functions

Add docstrings as a triple-quoted string beginning with the first line of a function or method. See epydoc for a suggested format.

6.2   lambda

Use a lambda, as a convenience, when you need a function that both:

  • is anonymous and
  • contains only an expression and no statements.

A lambda can take multiple arguments and can return (like a function) multiple values. Example:

In [79]: a = lambda x, y: (x * 3, y * 4, (x, y))
In [80]:
In [81]: a(3, 4)
Out[81]: (9, 16, (3, 4))

Suggestion: In some cases, a lambda may be useful as an event handler.

Example:

class Test:
    def __init__(self, first='', last=''):
        self.first = first
        self.last = last
    def test(self, formatter):
        """
        Test for lambdas.
        formatter is a function taking 2 arguments, first and last
          names.  It should return the formatted name.
        """
        msg = 'My name is %s' % (formatter(self.first, self.last),)
        print msg

def test():
    t = Test('Dave', 'Kuhlman')
    t.test(lambda first, last: '%s %s' % (first, last, ))
    t.test(lambda first, last: '%s, %s' % (last, first, ))

test()

Reference: http://docs.python.org/ref/lambdas.html

6.3   Iterators and generators

Concepts:

iterator
And iterator is something that satisfies the iterator protocol.
generator
A generator is a class or function that implements an iterator, i.e. that implements the iterator protocol.
the iterator protocol

An object satisfies the iterator protocol if it does the following:

  • It implements a __iter__ method, which returns an iterator object.
  • It implements a next function, which returns the next item from the collection, sequence, stream, etc of items to be iterated over
  • It raises the StopIteration exception when the items are exhausted and the next() method is called.

For more information on iterators, see the section on iterator types in the Python Library Reference -- http://docs.python.org/lib/typeiter.html.

For more on the yield statement, see: The yield statement -- http://docs.python.org/ref/yield.html.

For more on yield expressions and on the next() and send() generator methods, as well as others, see: Yield expression -- http://docs.python.org/ref/yieldexpr.html in the Python reference manual.

A function or method containing a yield statement implements a generator. Adding the yield statement to a function or method turns that function or method into one which, when called, returns a generator, i.e. an object that implements the iterator protocol.

A generator (a function containing yield) provides a convenient way to implement a filter. But, also consider:

  • The filter() built-in function
  • List comprehensions with an if clause

Here are a few examples:

# For Jython
from __future__ import generators        # Note 1

def simplegenerator():
    yield 'aaa'                          # Note 2
    yield 'bbb'
    yield 'ccc'

def list_tripler(somelist):
    for item in somelist:
        item *= 3
        yield item

def limit_iterator(somelist, max):
    for item in somelist:
        if item > max:
            return                       # Note 3
        yield item

def test():
    print '1.', '-' * 30
    it = simplegenerator()
    for item in it:
        print item
    print '2.', '-' * 30
    alist = range(5)
    it = list_tripler(alist)
    for item in it:
        print item
    print '3.', '-' * 30
    alist = range(8)
    it = limit_iterator(alist, 4)
    for item in it:
        print item
    print '4.', '-' * 30
    it = simplegenerator()
    try:
        print it.next()                  # Note 4
        print it.next()
        print it.next()
        print it.next()
    except StopIteration, exp:           # Note 5
        print 'reached end of sequence'

if __name__ == '__main__':
    test()

Notes:

  1. In order to use generators and the yield statement in Jython 2.2, we need to add the following import statement:

    from __future__ import generators
    
  2. The yield statement returns a value. When the next item is requested and the iterator is "resumed", execution continues immediately after the yield statement.

  3. We can terminate the sequence generated by an iterator by using a return statement with no value.

  4. To resume a generator, use the generator's next() or send() methods. send() is like next(), but provides a value to the yield expression.

  5. We can alternatively obtain the items in a sequence by calling the iterator's next() method. Since an iterator is a first-class object, we can save it in a data structure and can pass it around for use at different locations and times in our program.

  6. When an iterator is exhausted or empty, it throws the StopIteration exception, which we can catch.

And here is the output from running the above example:

$ python test_iterator.py
1. ------------------------------
aaa
bbb
ccc
2. ------------------------------
0
3
6
9
12
3. ------------------------------
0
1
2
3
4
4. ------------------------------
aaa
bbb
ccc
reached end of sequence

An instance of a class which implements the __iter__ method, returning an iterator, is iterable. For example, it can be used in a for statement or in a list comprehension, or in a generator expression, or as an argument to the iter() built-in method. But, notice that the class most likely implements a generator method which can be called directly.

Examples -- The following code implements an iterator that produces all the objects in a tree of objects:

class Node:
    def __init__(self, data, children=None):
        self.initlevel = 0
        self.data = data
        if children is None:
            self.children = []
        else:
            self.children = children
    def set_initlevel(self, initlevel): self.initlevel = initlevel
    def get_initlevel(self): return self.initlevel
    def addchild(self, child):
        self.children.append(child)
    def get_data(self):
        return self.data
    def get_children(self):
        return self.children
    def show_tree(self, level):
        self.show_level(level)
        print 'data: %s' % (self.data, )
        for child in self.children:
            child.show_tree(level + 1)
    def show_level(self, level):
        print '   ' * level,
    #
    # Generator method #1
    # This generator turns instances of this class into iterable objects.
    #
    def walk_tree(self, level):
        yield (level, self, )
        for child in self.get_children():
            for level1, tree1 in child.walk_tree(level+1):
                yield level1, tree1
    def __iter__(self):
        return self.walk_tree(self.initlevel)


#
# Generator method #2
# This generator uses a support function (walk_list) which calls
#   this function to recursively walk the tree.
# If effect, this iterates over the support function, which
#   iterates over this function.
#
def walk_tree(tree, level):
    yield (level, tree)
    for child in walk_list(tree.get_children(), level+1):
        yield child

def walk_list(trees, level):
    for tree in trees:
        for tree in walk_tree(tree, level):
            yield tree


#
# Generator method #3
# This generator is like method #2, but calls itself (as an iterator),
#   rather than calling a support function.
#
def walk_tree_recur(tree, level):
    yield (level, tree,)
    for child in tree.get_children():
        for level1, tree1 in walk_tree_recur(child, level+1):
            yield (level1, tree1, )


def show_level(level):
    print '   ' * level,


def test():
    a7 = Node('777')
    a6 = Node('666')
    a5 = Node('555')
    a4 = Node('444')
    a3 = Node('333', [a4, a5])
    a2 = Node('222', [a6, a7])
    a1 = Node('111', [a2, a3])
    initLevel = 2
    a1.show_tree(initLevel)
    print '=' * 40
    for level, item in walk_tree(a1, initLevel):
        show_level(level)
        print 'item:', item.get_data()
    print '=' * 40
    for level, item in walk_tree_recur(a1, initLevel):
        show_level(level)
        print 'item:', item.get_data()
    print '=' * 40
    a1.set_initlevel(initLevel)
    for level, item in a1:
        show_level(level)
        print 'item:', item.get_data()
    iter1 = iter(a1)
    print iter1
    print iter1.next()
    print iter1.next()
    print iter1.next()
    print iter1.next()
    print iter1.next()
    print iter1.next()
    print iter1.next()
##    print iter1.next()
    return a1

if __name__ == '__main__':
    test()

Notes:

  • An instance of class Node is "iterable". It can be used directly in a for statement, a list comprehension, etc. So, for example, when an instance of Node is used in a for statement, it produces an iterator.
  • We could also call the Node.walk_method directly to obtain an iterator.
  • Method Node.walk_tree and functions walk_tree and walk_tree_recur are generators. When called, they return an iterator. They do this because they each contain a yield statement.
  • These methods/functions are recursive. They call themselves. Since they are generators, they must call themselves in a context that uses an iterator, for example in a for statement.

6.4   Modules

A module is a Python source code file.

A module can be imported. When imported, the module is evaluated, and a module object is created. The module object has attributes. The following attributes are of special interest:

  • __doc__ -- The doc string of the module.
  • __name__ -- The name of the module when the module is imported, but the string "__main__" when the module is executed.
  • Other names that are created (bound) in the module.

A module can be run.

To make a module both import-able and run-able, use the following idiom (at the end of the module):

def main():
    o
    o
    o

if __name__ == '__main__':
    main()

Where Python looks for modules:

  • See sys.path.
  • Standard places.
  • Environment variable PYTHONPATH.
  • Jython only: See registry file.

Notes about modules and objects:

  • A module is an object.
  • A module (object) can be shared.
  • A specific module is imported only once in a single run. This means that a single module object is shared by all the modules that import it.

6.4.1   Doc strings for modules

Add docstrings as a triple-quoted string at or near the top of the file. See epydoc for a suggested format.

6.5   Packages

A package is a directory on the file system which contains a file named __init__.py.

The __init__.py file:

  • Why is it there? -- It makes modules in the directory "import-able".

  • Can __init__.py be empty? -- Yes. Or, just include a comment.

  • When is it evaluated? -- It is evaluated the first time that an application imports anything from that directory/package.

  • What can you do with it? -- Any code that should be executed exactly once and during import. For example:

    • Perform initialization needed by the package.
    • Make variables, functions, classes, etc available. For example, when the package is imported rather than modules in the package. You can also expose objects defined in modules contained in the package.
  • Define a variable named __all__ to specify the list of names that will be imported by from my_package import *. For example, if the following is present in my_package/__init__.py:

    __all__ = ['func1', 'func2',]
    

    Then, from my_package import * will import func1 and func2, but not other names defined in my_package.

    Note that __all__ can be used at the module level, as well as at the package level.

6.6   Debugging tools

pdb -- The Python debugger:

  • Start the debugger by running an expression:

    pdb.run('expression')
    

    Example:

    if __name__ == '__main__':
        import pdb
        pdb.run('main()')
    
  • Start up the debugger at a specific location with the following:

    import pdb; pdb.set_trace()
    

    Example:

    if __name__ == '__main__':
        import pdb
        pdb.set_trace()
        main()
    
  • Get help from within the debugger. For example:

    (Pdb) help
    (Pdb) help next
    

Can also embed IPython into your code. See http://ipython.scipy.org/doc/manual/manual.html.

Inspecting:

Miscellaneous tools:

  • id(obj)
  • globals(), locals().
  • dir(obj) -- Returns interesting names, but list is not necessarily complete.
  • obj.__class__
  • cls.__bases__
  • obj.__class__.__bases__
  • obj.__doc__
  • Customize the representation of your class. Define the following methods in your class:
    • __repr__() -- Called by (1) repr(), (2) interactive interpreter when representation is needed.
    • __str__() -- Called by (1) str(), (2) string formatting.

pdb is implemented with the cmd module in the Python standard library. You can implement similar command line interfaces by using cmd. See: cmd -- Support for line-oriented command interpreters -- http://docs.python.org/lib/module-cmd.html.

7   Classes

Classes model the behavior of objects in the "real" world. Methods implement the behaviors of these types of objects. Member variables hold (current) state. Classes enable us to implement new data types in Python.

The class: statement is used to define a class. The class: statement creates a class object and binds it to a name.

7.1   A simple class

In [104]: class A:
   .....:     pass
   .....:
In [105]: a = A()

To define a new style class (recommended), inherit from object or from another class that does. Example:

In [21]: class A(object):
   ....:     pass
   ....:
In [22]:
In [22]: a = A()
In [23]: a
Out[23]: <__main__.A object at 0x82fbfcc>

7.2   Defining methods

A method is a function defined in class scope and with first parameter self:

In [106]: class B:
   .....:     def show(self):
   .....:         print 'hello from B'
   .....:
In [107]: b = B()
In [108]: b.show()
hello from B

A method as we describe it here is more properly called an instance method, in order to distinguish it from class methods and static methods.

7.3   The constructor

The constructor is a method named __init__.

Exercise: Define a class with a member variable name and a show method. Use print to show the name. Solution:

In [109]: class A:
   .....:       def __init__(self, name):
   .....:         self.name = name
   .....:   def show(self):
   .....:         print 'name: "%s"' % self.name
   .....:
In [111]: a = A('dave')
In [112]: a.show()
name: "dave"

Notes:

  • The self variable is explicit.
  • The spelling ("self") is optional, but everyone spells it that way.

7.4   Member variables

Defining member variables -- Member variables are created with assignment. Example:

class A:
    def __init__(self, name):
        self.name = name

A small gotcha -- Do this:

In [28]: class A:
   ....:     def __init__(self, items=None):
   ....:         if items is None:
   ....:             self.items = []
   ....:         else:
   ....:             self.items = items

Do not do this:

In [29]: class B:
   ....:     def __init__(self, items=[]):   # wrong.  list ctor evaluated only once.
   ....:         self.items = items

In the second example, the def statement and the list constructor are evaluated only once. Therefore, the item member variable of all instances of class B, will share the same value, which is most likely not what you want.

7.5   Calling methods

  • Use the instance and the dot operator.
  • Calling a method defined in the same class or a super-class:
    • Same class: use self.
    • Super-class: use the class (name).
  • Calling a method defined in a specific super-class -- Use the class (name).

7.6   Adding inheritance

Referencing super-classes -- Use the name of the super-class, for example:

In [39]: class B(A):
   ....:     def __init__(self, name, size):
   ....:         A.__init__(self, name)
   ....:         self.self = size

Calling the constructor of the super-class.

You can also use multiple inheritance. Example:

class C(A, B):
    ...

Python searches super-classes in left-to-right depth-first order.

For more information on inheritance, see the tutorial in the standard Python documentation set: 9.5 Inheritance and 9.5.1 Multiple Inheritance.

Watch out for problems with inheriting from classes that have a common base class.

7.7   Class variables

  • Also called static data.

  • Define at class level with assignment. Example:

    class A:
        size = 5
        def get_size(self):
            return A.size
    
  • Reference with classname.variable.

  • Caution: self.variable = x creates a new member variable.

7.8   Class methods and static methods

Instance (plain) methods:

  • An instance method receives the instance as its first argument.

Class methods:

  • A class method receives the class as its first argument.
  • Define class methods with builtin function classmethod() or with decorator @classmethod.
  • See the description of classmethod() built-in function at "Built-in Functions": http://docs.python.org/lib/built-in-funcs.html#l2h-16

Static methods:

  • A static method receives neither the instance nor the class as its first argument.
  • Define class methods with builtin function staticmethod() or with decorator @staticmethod.
  • See the description of staticmethod() built-in function at "Built-in Functions": http://docs.python.org/lib/built-in-funcs.html#l2h-69

Notes on decorators:

  • A decorator of the form @afunc is the same as m = afunc(m). So, this:

    @afunc
    def m(self): pass
    

    is the same as:

    def m(self): pass
    m = afunc(m)
    
  • You can use @classmethod and @staticmethod (instead of the classmethod() and staticmethod() built-in functions) to declare class methods and static methods.

  • Decorators are not yet available in Jython.

Example:

class B:
    def dup_string(x):
        s1 = '%s%s' % (x, x,)
        return s1
    dup_string = staticmethod(dup_string)

B.dup_string('abcd')
'abcdabcd'

An alternative way to implement static methods -- Use a "plain", module-level function. For example:

In [1]: def inc_count():
   ...:         A.count += 1
   ...:
In [2]:
In [2]: def dec_count():
   ...:         A.count -= 1
   ...:
In [3]:
In [3]: class A:
   ...:         count = 0
   ...:     def get_count(self):
   ...:             return A.count
   ...:
In [4]:
In [4]: a = A()
In [5]: a.get_count()
Out[5]: 0
In [6]:
In [6]:
In [6]: inc_count()
In [7]: inc_count()
In [8]: a.get_count()
Out[8]: 2
In [9]:
In [9]: b = A()
In [10]: b.get_count()
Out[10]: 2

7.9   Properties

The property built-in function enables us to write classes in a way that does not require a user of the class to use getters and setters. Example:

class TestProperty(object):
    def __init__(self, description):
        self._description = description
    def set_description(self, description):
        print 'setting description'
        self._description = description
    def get_description(self):
        print 'getting description'
        return self._description
    description = property(get_description, set_description)

For more information on properties, see Built-in Functions -- http://docs.python.org/lib/built-in-funcs.html#l2h-57

7.10   Interfaces

In Python, to implement an interface is to implement a method with a specific name and a specific arguments.

"Duck typing" -- If it walks like a duck and quacks like a duck ...

One way to define an "interface" is to define a class containing methods that have a header and a doc string but no implementation.

Additional notes on interfaces:

  • Interfaces are not enforced.
  • A class does not have to implement all of an interface.

7.11   New-style classes

A new-style class is one that sub-classes object or a class that sub-classes object or another new-style class.

You can sub-class Python's built-in data-types.

  • A simple example -- the following class extends the list data-type:

    class C(list):
      def get_len(self):
        return len(self)
    
    c = C((11,22,33))
    c.get_len()
    
    c = C((11,22,33,44,55,66,77,88))
    print c.get_len()
    # Prints "8".
    
  • A slightly more complex example -- the following class extends the dictionary data-type:

    class D(dict):
        def __init__(self, data=None, name='no_name'):
            if data is None:
                data = {}
            dict.__init__(self, data)
            self.name = name
        def get_len(self):
            return len(self)
        def get_keys(self):
            content = []
            for key in self:
                content.append(key)
            contentstr = ', '.join(content)
            return contentstr
        def get_name(self):
            return self.name
    
    def test():
        d = D({'aa': 111, 'bb':222, 'cc':333})
        # Prints "3"
        print d.get_len()
        # Prints "'aa, cc, bb'"
        print d.get_keys()
        # Prints "no_name"
        print d.get_name()
    

Some things to remember about new-style classes:

  • In order to be new-style, a class must inherit (directly or indirectly) from object. Note that if you inherit from a built-in type, you get this automatically.
  • New-style classes unify types and classes.
  • You can sub-class (built-in) types such as dict, str, list, file, etc.
  • The built-in types now provide factory functions: dict(), str(), int(), file(), etc.
  • The built-in types are introspect-able -- Use x.__class__, dir(x.__class__), isinstance(x, list), etc.
  • New-style classes give you properties and descriptors.
  • New-style classes enable you to define static methods. Actually, all classes enable you to do this.
  • A new-style class is a user-defined type. For an instance of a new-style class x, type(x) is the same as x.__class__.

For more on new-style classes, see:

Exercises:

  • Write a class and a sub-class of this class.

    • Give the super-class one member variable, a name, which can be entered when an instance is constructed.
    • Give the sub-class one member variable, a description; the sub-class constructor should allow entry of both name and description.
    • Put a show() method in the super-class and override the show() method in the sub-class.

    Solution:

    class A(object):
        def __init__(self, name):
            self.name = name
        def show(self):
            print 'name: %s' % (self.name, )
    
    class B(A):
        def __init__(self, name, desc):
            A.__init__(self, name)
            self.desc = desc
        def show(self):
            A.show(self)
            print 'desc: %s' % (self.desc, )
    

7.12   Doc strings for classes

Add docstrings as a (triple-quoted) string beginning with the first line of a class. See epydoc for a suggested format.

8   Special Tasks

8.1   File input and output

Create a file object. Use file() (or open() in Jython prior to Jython 2.2).

This example reads and prints each line of a file:

def test():
    f = file('tmp.py', 'r')
    for line in f:
        print 'line:', line.rstrip()
    f.close()

test()

Notes:

  • A text file is an iterable. It iterates over the lines in a file. The following is a common idiom:

    infile = file(filename, 'r')
    for line in infile:
        process_a_line(line)
    infile.close()
    
  • string.rstrip() strips new-line and other whitespace from the right side of each line. To strip new-lines only, but not other whitespace, try rstrip('\n').

  • Other ways of reading from a file/stream object: my_file.read(), my_file.readline(), my_file.readlines(),

This example writes lines of text to a file:

def test():
    f = file('tmp.txt', 'w')
    for ch in 'abcdefg':
        f.write(ch * 10)
        f.write('\n')
    f.close()

test()

Notes:

  • The write method, unlike the print statement, does not automatically add new-line characters.
  • Must close file in order to flush output. Or, use my_file.flush().

8.2   Unit tests

For more documentation on the unit test framework, see 5.3 unittest -- Unit testing framework -- http://docs.python.org/lib/module-unittest.html.

For help and more information do the following at the Python interactive prompt:

>>> import unittest
>>> help(unittest)

And, you can read the source: Lib/unittest.py in the Python standard library.

8.2.1   A simple example

Here is a very simple example. You can find more information about this primitive way of structuring unit tests in the library documentation for the unittest module Basic example -- http://docs.python.org/lib/minimal-example.html

import unittest

class UnitTests02(unittest.TestCase):

    def testFoo(self):
        self.failUnless(False)

class UnitTests01(unittest.TestCase):

    def testBar01(self):
        self.failUnless(False)

    def testBar02(self):
        self.failUnless(False)

def main():
    unittest.main()

if __name__ == '__main__':
    main()

Notes:

  • The call to unittest.main() runs all tests in all test fixtures in the module. It actually creates an instance of class TestProgram in module Lib/unittest.py, which automatically runs tests.

  • Test fixtures are classes that inherit from unittest.TestCase.

  • Within a test fixture (a class), the tests are any methods whose names begin with the prefix "test".

  • In any test, we check for success or failure with inherited methods such as failIf(), failUnless(), assertNotEqual(), etc. For more on these methods, see the library documentation for the unittest module TestCase Objects -- http://docs.python.org/lib/testcase-objects.html.

  • If you want to change (1) the test method prefix or (2) the function used to sort (the order of) execution of tests within a test fixture, then you can create your own instance of class unittest.TestLoader and customize it. For example:

    def main():
        my_test_loader = unittest.TestLoader()
        my_test_loader.testMethodPrefix = 'check'
        my_test_loader.sortTestMethodsUsing = my_cmp_func
        unittest.main(testLoader=my_test_loader)
    
    if __name__ == '__main__':
        main()
    

    But, see the notes in section Additional unittest features for instructions on a (possibly) better way to do this.

8.2.2   Unit test suites

Here is another, not quite so simple, example:

#!/usr/bin/env python

import sys, popen2
import getopt
import unittest


class GenTest(unittest.TestCase):

    def test_1_generate(self):
        cmd = 'python ../generateDS.py -f -o out2sup.py -s out2sub.py people.xsd'
        outfile, infile = popen2.popen2(cmd)
        result = outfile.read()
        outfile.close()
        infile.close()
        self.failUnless(len(result) == 0)

    def test_2_compare_superclasses(self):
        cmd = 'diff out1sup.py out2sup.py'
        outfile, infile = popen2.popen2(cmd)
        outfile, infile = popen2.popen2(cmd)
        result = outfile.read()
        outfile.close()
        infile.close()
        #print 'len(result):', len(result)
        # Ignore the differing lines containing the date/time.
        #self.failUnless(len(result) < 130 and result.find('Generated') > -1)
        self.failUnless(check_result(result))

    def test_3_compare_subclasses(self):
        cmd = 'diff out1sub.py out2sub.py'
        outfile, infile = popen2.popen2(cmd)
        outfile, infile = popen2.popen2(cmd)
        result = outfile.read()
        outfile.close()
        infile.close()
        # Ignore the differing lines containing the date/time.
        #self.failUnless(len(result) < 130 and result.find('Generated') > -1)
        self.failUnless(check_result(result))


def check_result(result):
    flag1 = 0
    flag2 = 0
    lines = result.split('\n')
    len1 = len(lines)
    if len1 <= 5:
        flag1 = 1
    s1 = '\n'.join(lines[:4])
    if s1.find('Generated') > -1:
        flag2 = 1
    return flag1 and flag2


# Make the test suite.
def suite():
    # The following is obsolete.  See Lib/unittest.py.
    #return unittest.makeSuite(GenTest)
    loader = unittest.TestLoader()
    # or alternatively
    # loader = unittest.defaultTestLoader
    testsuite = loader.loadTestsFromTestCase(GenTest)
    return testsuite


# Make the test suite and run the tests.
def test():
    testsuite = suite()
    runner = unittest.TextTestRunner(sys.stdout, verbosity=2)
    runner.run(testsuite)


USAGE_TEXT = """
Usage:
    python test.py [options]
Options:
    -h, --help      Display this help message.
Example:
    python test.py
"""

def usage():
    print USAGE_TEXT
    sys.exit(-1)


def main():
    args = sys.argv[1:]
    try:
        opts, args = getopt.getopt(args, 'h', ['help'])
    except:
        usage()
    relink = 1
    for opt, val in opts:
        if opt in ('-h', '--help'):
            usage()
    if len(args) != 0:
        usage()
    test()


if __name__ == '__main__':
    main()
    #import pdb
    #pdb.run('main()')

Notes:

8.2.3   Additional unittest features

And, the following example shows several additional features. See the notes that follow the code:

import unittest

class UnitTests02(unittest.TestCase):
    def testFoo(self):
        self.failUnless(False)
    def checkBar01(self):
        self.failUnless(False)


class UnitTests01(unittest.TestCase):
    # Note 1
    def setUp(self):
        print 'setting up UnitTests01'
    def tearDown(self):
        print 'tearing down UnitTests01'
    def testBar01(self):
        print 'testing testBar01'
        self.failUnless(False)
    def testBar02(self):
        print 'testing testBar02'
        self.failUnless(False)

def function_test_1():
    name = 'mona'
    assert not name.startswith('mo')

def compare_names(name1, name2):
    if name1 < name2:
        return 1
    elif name1 > name2:
        return -1
    else:
        return 0

def make_suite():
    suite = unittest.TestSuite()
    # Note 2
    suite.addTest(unittest.makeSuite(UnitTests01, sortUsing=compare_names))
    # Note 3
    suite.addTest(unittest.makeSuite(UnitTests02, prefix='check'))
    # Note 4
    suite.addTest(unittest.FunctionTestCase(function_test_1))
    return suite

def main():
    suite = make_suite()
    runner = unittest.TextTestRunner()
    runner.run(suite)

if __name__ == '__main__':
    main()

Notes:

  1. If you run this code, you will notice that the setUp and tearDown methods in class UnitTests01 are run before and after each test in that class.
  2. We can control the order in which tests are run by passing a compare function to the makeSuite function. The default is the cmp builtin function.
  3. We can control which methods in a test fixture are selected to be run by passing the optional argument prefix to the makeSuite function.
  4. If we have an existing function that we want to "wrap" and run as a unit test, we can create a test case from a function with the FunctionTestCase function. If we do that, notice that we use the assert statement to test and possibly cause failure.

8.2.4   Guidance on Unit Testing

Why should we use unit tests? Many reasons, including:

8.3   doctest

For simple test harnesses, consider using doctest. With doctest you can (1) run a test at the Python interactive prompt, then (2) copy and paste that test into a doc string in your module, and then (3) run the tests automatically from within your module under doctest.

There are examples and explanation in the standard Python documentation: 5.2 doctest -- Test interactive Python examples -- http://docs.python.org/lib/module-doctest.html.

A simple way to use doctest in your module:

  1. Run several tests in the Python interactive interpreter. Note that because doctest looks for the interpreter's ">>>" prompt, you must use the standard interpreter, and not, for example, IPython. Also, make sure that you include a line with the ">>>" prompt after each set of results; this enables doctest to determine the extent of the test results.

  2. Use copy and paste, to insert the tests and their results from your interactive session into the docstrings.

  3. Add the following code at the bottom of your module:

    def _test():
        import doctest
        doctest.testmod()
    
    if __name__ == "__main__":
        _test()
    

    For Jython, you might need to use the following instead:

    import doctest, mymodule
    doctest.testmod(mymodule)
    

Here is an example:

def f(n):
    """
    Print something funny.

    >>> f(1)
    10
    >>> f(2)
    -10
    >>> f(3)
    0
    """
    if n == 1:
        return 10
    elif n == 2:
        return -10
    else:
        return 0


def test():
    import doctest, test_doctest
    doctest.testmod(test_doctest)

if __name__ == '__main__':
    test()

And, here is the output from running the above test with the -v flag:

$ jython test_doctest.py -v
Running test_doctest.__doc__
0 of 0 examples failed in test_doctest.__doc__
Running test_doctest.f.__doc__
Trying: f(1)
Expecting: 10
ok
Trying: f(2)
Expecting: -10
ok
Trying: f(3)
Expecting: 0
ok
0 of 3 examples failed in test_doctest.f.__doc__
Running test_doctest.test.__doc__
0 of 0 examples failed in test_doctest.test.__doc__
2 items had no tests:
    test_doctest
    test_doctest.test
1 items passed all tests:
   3 tests in test_doctest.f
3 tests in 3 items.
3 passed and 0 failed.
Test passed.

8.4   Installing Python packages

Simple:

$ python setup.py build
$ python setup.py install    # as root

More complex:

9   More Python Features and Exercises

[As time permits, explain more features and do more exercises as requested by class members.]

Thanks to David Goodger for the following list or references. His "Code Like a Pythonista: Idiomatic Python" (http://python.net/~goodger/projects/pycon/2007/idiomatic/) is worth a careful reading: