Jython Course Outline

Author: Dave Kuhlman
Address:
dkuhlman@rexx.com
http://www.rexx.com/~dkuhlman
Revision: 1.0a
Date: June 30, 2006
Copyright: Copyright (c) 2006 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 an outline of an introductory course on programming in Jython.

Contents

1   Introductions Etc

Introductions

Practical matters

Starting the Python interactive interpreter. Also look at IPython.

Running scripts

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

Interactive interpreters:

1.1   Resources

Where else to get help:

2   What is Python?

A general description of Python:

3   What is Jython?

Jython is Python:

Jython is Java:

Why and when we should use Jython -- Use Jython, instead of Python, when:

How Jython compares with Java:

How Jython compares with Python:

A comparison of Java and Python is here: Python & Java: a Side-by-Side Comparison.

4   Differences between Jython and CPython

5   Lexical matters

5.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 semicolon, 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 triple quotes at the beginning of a module, class, method or function.

5.2   Names and tokens

  • Allowed characters: a-z A-Z 0-9 underscore, and must begin with a letter or underscore.
  • Case is significant in names and identifiers.
  • 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 -- All upper case.
    • Classes -- Bumpy caps with initial upper.
    • Methods and functions -- All lower case with words separated by underscores.
    • Local variables -- Lower case or bumpy caps with initial lower or your choice.

5.3   Blocks and indentation

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

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. They have to.
  • Reduces work. Only need to get the indentation correct, not both indentation and brackets.
  • Reduces clutter. Eliminates all those 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. There is a list of suitable text editors at: PythonEditors.

5.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() (standard Python only?), obj.__doc__, and, in IPython, ?.

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:

5.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.

5.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.

  • Since these operators can be defined in each object type and class, the meaning of each operator depends on the type of the object to which it is applied.

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

  • Here is a demonstration of the relationship between some of the operators and the methods that define them:

    class B:
        def __init__(self):
            self.val = 'aaa'
        def __add__(self, val1):
            """Operator: + """
            return '%s||%s' % (self.val, val1,)
        def __neg__(self):
            """Operator: - """
            return 'neg<%s>' % self.val
        def __pow__(self, p):
            """Operator: ** """
            return 'pow<%s||%s>' % (self.val, p, )
        def __invert__(self):
            """Operator: ~ """
            return 'invert<%s>' % self.val
        def __lshift__(self, count):
            """Operator: << """
            return 'lshift<%s||%s>' % (self.val, count, )
        def __and__(self, x):
            """Operator: & """
            return 'and<%s||%s>' % (self.val, x, )
        def __or__(self, x):
            """Operator: | """
            return 'or<%s||%s>' % (self.val, x, )
        def __xor__(self, x):
            """Operator: ^ """
            return 'xor<%s||%s>' % (self.val, x, )
        def __mod__(self, x):
            """Operator: % """
            return 'mod<%s||%s>' % (self.val, x, )
        def __contains__(self, x):
            """Operator: in """
            if len(x) == len(self.val):
                return True
            else:
                return False
    
    
    def test():
        b = B()
        print b + 'bbb'         # __add__       Addition
        print - b               # __neg__       Negation
        print b ** 'ccc'        # __pow__       Power (a raised to the power b)
        print ~ b               # __invert__    Bitwise invert
        print b << 3            # __lshift__    Left shift
        print b & 3             # __and__       Bitwise and
        print b | 3             # __or__        Bitwise or
        print b ^ 3             # __xor__       Exclusive bitwise or
        print b % 3             # __xor__       a modulo b
        print 'abc' in b        # __contains__  b in a (note reversed operands)
        print 'ab' in b
    
    test()
    

    Running the above code produces the following output:

    aaa||bbb
    neg<aaa>
    pow<aaa||ccc>
    invert<aaa>
    lshift<aaa||3>
    and<aaa||3>
    or<aaa||3>
    xor<aaa||3>
    mod<aaa||3>
    True
    False
    

Later, we will see how these operators can be emulated in classes that you define yourself.

5.7   Code evaluation

Creating names/variables -- The following all create names (variables): (1) assignment, (2) function definition, (3) class definition, (4) module import, ...

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.
  • del() -- The built-in function del() removes a reference, not (necessarily) the object itself.

6   Built-in datatypes

6.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.

Python does mixed arithmetic.

Integer division truncates.

6.2   Tuples and lists

Tuples and lists are sequences.

Tuple constructor -- ().

List constructor -- [].

Tuples are like lists, but are not mutable.

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 continuation character "").
  • A comma can follow the last element.

Length -- Get the length of a sequence with the built-in function len().

Subscription:

  • Indexing into a sequence
  • Negative indexes -- Effectively, length of sequence plus index.
  • Slicing -- Example: data[2:5].
  • Slicing with strides -- Example: data[::2].

Operations on tuples -- No operations that change the tuple, since tuples are immutable. We can do iteration. And, we can do subscription (access only).

Operations on lists -- Operations similar to tuples plus:

  • Insert -- mylist.insert(index, newitem).
  • Append -- mylist.append(newitem).
  • Remove -- mylist.remove(item) and mylist.pop(). Note that append() together with pop() implements a stack.
  • Delete -- del mylist[index].

Use the built-in function len() to get the length of a sequence. It works on tuples, lists, strings, dictionaries, etc.

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'
    

6.3   Strings

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

Constructors/literals:

  • Quotes: single and double. Escape quotes and other special characters with a back-slash.
  • Triple quoting -- Multi-line quotes.
  • str() -- The constructor and the name of the type/class.
  • String escape sequences: \t, \n, \', \", \ooo (octal), \xhh (hex), etc. For more escape sequences, see 2.4.1 String literals: http://docs.python.org/ref/strings.html.

String methods:

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 reallocation. 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

6.4   Dictionaries

A dictionary is a sequence, whose values are accessible by key. Another view: A dictionary is a set of name-value pairs.

Keys may be any non-mutable type.

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.

Literals for constructing dictionaries:

{key1: value1, key2: value2, }

Constructor for dictionaries: dict() (Jython 2.2 and later).

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

>>> help({})        # Python, but not Jython.

Or:

>>> dir({})

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

>>> d = {'aa': 111, 'bb': 222}
>>> d.keys()
['aa', 'bb']
>>> d.values()
[111, 222]
>>> d.items()
[('aa', 111), ('bb', 222)]

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:

  • Iterators are supported Jython 2.2a, but not by Jython 2.1.

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

    In Jython 2.1, use mydict.keys(), mydict.values(), and mydict.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
    

    But, in Jython 2.1, use:

    for k in myDict.keys(): print k
    
  • The in operator tests for a key in a dictionary (but not in Jython 2.1). Example:

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

    In Jython 2.1, use mydict.has_key(key).

6.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 = open('mylog.txt', 'r')
In [34]: for line in f:
   ....:     print line,
   ....:
message #1
message #2
message #3
In [35]: f.close()

Notes:

  • A file object supports the iterator protocol and, therefore, can be used in a for statement. This is true of Jython 2.2a1, but in Jython 2.1 you will need to use myfile.readlines().
  • You will sometimes see the use of file() instead of open(). The newer form is file. But, open is still recommended. With built-in types, we can use the type name as the constructor.
  • Lines read from a text file have a newline character. Strip it off with something like: b.rstrip('\n').
  • Learn more about file objects and the methods they provide at: 2.3.9 File Objects.

You can also append to an existing file. In order to do so, open the file in "append" mode. Example:

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

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 = file('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().

7   Statements Part 1

7.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
    

    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. Example:

    >>> x = range(5)
    >>> print x
    [0, 1, 2, 3, 4]
    >>> x[2] = 10
    >>> print x
    [0, 1, 10, 3, 4]
    
  • A slice of a sequence -- Note that the sequence must be mutable. Example:

    >>> x = range(5)
    >>> print x
    [0, 1, 2, 3, 4]
    >>> x[2:4] = (11, 12)
    >>> print x
    [0, 1, 11, 12, 4]
    
  • 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 creates a new variable (if it does not exist in the namespace) and a binding. Specifically, it binds a value to the (possibly 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).

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

    a = b = 3
    
  • Jython/Python does not have the concept of constants. Use global variables and assignment instead. Examples:

    NOCOLOR, RED, GREEN, BLUE = range(4)
    DEFAULT_CONFIG_NAME = 'defaults.config'
    

7.2   import

Make module available.

What import does:

  • Evaluate the content of a module.
  • Likely to create variables in the local (module) namespace.
  • Evaluation only happens once during a given run of the program.
  • 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, ...

Where import looks for modules:

  • The current directory.
  • CPython (not Jython): directories in PYTHONPATH environment variable.
  • Jython (not Python): directories in python.path in the Jython registry.
  • sys.path shows where it looks. A script can modify and add to sys.path, but that is usually not the way to make directories available to import.
  • Packages need a file named __init__.py. If a directory is not directly in sys.path but is under a directory in sys.path, then it will need a __init__.py so that modules can be imported from it.

Forms of the import statement:

  • import A -- Names in the local (module) namespace are accessible with the dot operator.
  • 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: mixes name-spaces.
  • from A import B as C

The import statement and packages -- __init__.py. What is made available when you do import aPackage?

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

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 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 or class in simple.py.

Jython can import Java "modules" from jar files. The jar file must be on your classpath.

CPython can import modules stored in a Zip file. Here are a few notes:

  • Add modules to a zip file with any zip tool.

  • The zip file can contain other file types in addition to Jython/Python modules.

  • Add the zip file to PYTHONPATH or to sys.path. Example:

    import sys
    sys.path.append('~/Modules/myzippedmodules.zip')
    
  • Import the module in the normal way.

  • See 3.22 zipimport -- Import modules from Zip archives. The functionality described there is built-in to Jython and Python.

7.3   print

Arguments to print:

  • Multiple items -- Separated by commas.
  • End with comma to suppress carriage return.
  • Use string formatting for more control over output.
  • Also see various "pretty-printing" functions and methods, in particular, pprint. See http://docs.python.org/lib/module-pprint.html.

String formatting -- Arguments are a tuple. Reference: 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()

See the documentation on sys.stdout and sys.stdin: 3.1 sys -- System-specific parameters and functions (http://docs.python.org/lib/module-sys.html).

7.4   if: elif: else:

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

Truth values:

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

Operators:

  • and and or

  • not

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

    if x is None:
        ...
    
  • in -- Test for existence in a container and in particular in a dictionary. Example:

    >>> a = {'aa': 11, 'bb': 22}
    >>> 'bb' in a
    1
    >>> if 'aa' in a and a['aa']:
    ...   print 'good'
    ...
    good
    

    Note that Jython/Python uses short-circuit evaluation in conditions. See 5.10 Boolean operations (http://docs.python.org/ref/Booleans.html).

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.

7.5   try: except:

Caught and un-caught exceptions.

The try: statement catches an exception.

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

Exceptions are classes. They are sub-classes of class Exception.

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

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

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
    

7.6   raise

Throw or raise an exception.

Forms:

  • raise instance
  • raise MyExceptionClass, value
  • raise MyExceptionClass(value)

The raise statement takes:

  • 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).

A few examples:

In [29]: class MyException(Exception):
   ....:     pass
   ....:
In [30]: raise MyException, 'this is a test'
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in ?
MyException: this is a test

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)

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)

8   Statements Part 2

8.1   for

Iterate over a sequence or an "iterator" object.

Form -- for x in y:.

Note: Iterators are supported by Jython 2.2a, but not Jython 2.1.

Iterators:

  • Sequences are iterators.
  • Instances of classes that obey the iterator protocol are iterators. See http://docs.python.org/lib/typeiter.html.
  • Can create an iterator with iter().
  • An iterable implements the iterator interface and satisfies the iterator protocol. The iterator protocol: __iter__() and next() methods. See 2.3.5 Iterator Types.

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().
  • Sequences are iterable, for example, lists, tuples, dictionaries, strings,
  • Generator expressions -- Latest Python only. Syntactically like list comprehensions (surrounded by parens instead of square brackets), but use lazy evaluation.

Helpful functions with for:

  • enumerate(iterable) -- Returns an iterable that produces a pair (tuple) containing count and value. Example:

    for count, value in enumerate([11,22,33]):
        print count, value
    
  • 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.

8.2   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
    

8.3   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
    

8.4   del

What del does:

  • Removes names from namespace.
  • Removes an item from a collection, for example, a list or dictionary.
  • Remove an attribute from on object.

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 from a list or dictionary. 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'

9   Functions

9.1   Arguments

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 an argument with a default value, then all subsequent arguments for that function must have default values.

List arguments -- *args. It's a tuple. Example:

>>> def f(x, *args):
...   print 'x:', x
...   print 'args:', args
...
>>> f(11,22,33,44)
x: 11
args: (22, 33, 44)

Keyword arguments and default values -- **kwargs. It's a dictionary:

>>> def f(x, **kwargs):
...   print 'x:', x
...   print 'kwargs:', kwargs
...
>>> f(11, arg1=22, arg2=33, arg3=44)
x: 11
kwargs: {'arg3': 44, 'arg2': 33, 'arg1': 22}

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

>>> def f(x, *rest):
...   print 'x:', x
...   print 'rest:', rest
...
>>>
>>>
>>> f(11, a)
x: 11
rest: ([0, 1, 2, 3, 4],)
>>> f(11, *a)
x: 11
rest: (0, 1, 2, 3, 4)

Return values:

  • The default return value, if no return statement is executed, is None.

  • Use the return statement to return with a value.

  • You can return multiple values. A tuple is handy for this. Example:

    >>> def split_name(fullname):
    ...     names = fullname.split()
    ...     firstname = names[0]
    ...     lastname = names[1]
    ...     return firstname, lastname
    ...
    >>>
    >>> first, last = split_name('Dave Kuhlman')
    >>> print first
    Dave
    >>> print last
    Kuhlman
    

Local variables:

  • Creating local variables. Contrast with accessing a variable.
  • Variable look-up.
  • The global statement -- Must use global when we want to set the value of a global variable.

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.
  • Functions can take keyword arguments.
  • You can "capture" remaining arguments with *args, and **kwargs.
  • 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
    

9.2   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:

->> X = 3
/>> def t():
|..   print X
\__
->> t()
3
#
# No effect on global X.
/>> def s():
|..   X = 4
\__
->> s()
->> t()
3
/>> def u():
|..   global X
|..   X = 5
\__
->> u()
->> t()
5
#
# Error
# Must assign value before reference or declare as global.
/>> def v():
|..   x = X
|..   X = 6
|..   return x
\__
->> v()
Traceback (most recent call last):
  File "<input>", line 2, in ?
  File "<input>", line 3, in v
UnboundLocalError: local variable 'X' referenced before assignment
#
# This time, declare X as global.
/>> def w():
|..   global X
|..   x = X
|..   X = 7
|..   return x
\__
->> w()
5
->> X
7

9.2.1   Doc strings for functions

Add docstrings as a triple-quoted string beginning with the first line of a function or method. Access the documentation for a function through the __doc__ attribute. Example:

>>> def w():
...     """This is documentation on w.
...     It is simple.
...     """
...     print 'hi'
...
>>> w()
hi
>>> print w.__doc__
This is documentation on w.
    It is simple.

See epydoc for a suggested format.

9.3   lambda

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

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

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

9.4   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.

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.

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.

10   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.

10.1   A simple class

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

10.2   Creating instances

Call the class as though it were a function. Apply the function call operator () to the class. Example:

>>> anObj = MyNewClass()

You will need to add parameters to match the signature of the constructor. See below.

10.3   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

10.4   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.

10.5   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.

10.6   Methods

Defining methods -- Define methods as functions nested inside a class. The first argument is always self.

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). Examples:

    >>> self.calculate(maximum)
    >>> MySuperClass.calculate(maximum)
    

10.7   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

Note how we call 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.

10.8   Class variables

  • All instances of a class share the same class variable and its value.

  • 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.

10.9   Class methods

  • Also called static methods.

An alternative way to implement static methods (without new-style classes). Use a "plain", module-level function. For example:

>>> class A:
...   count = 0
...
>>> def inc_count():
...   A.count = A.count + 1
...
>>> def dec_count():
...   A.count = A.count - 1
...
>>> a = A()
>>> a.count
0
>>> inc_count()
>>> a.count
1
>>> b = A()
>>> b.count
1
>>> inc_count()
>>> inc_count()
>>> inc_count()
>>> a.count
4
>>> b.count
4

10.10   Interfaces

Other special names/methods -- __call__(), __getitem__(), setitem(), __cmp__(), __le__(), etc. See http://docs.python.org/ref/specialnames.html.

10.12   Doc strings

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

11   Modules, Packages, and Debugging

11.1   Modules

A module is a Python source code file.

A module can be imported.

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()

11.1.1   Doc strings for functions

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

11.2   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.

11.3   Debugging tools

pdb -- The Python debugger:

  • Start the debugger by running an expression:

    pdb.run('expression')
    

    Example:

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

    import pdb; pdb.set_trace()
    
  • Get help from within the debugger. For example:

    (Pdb) help
    (Pdb) help next
    

Miscellaneous tools:

  • id(obj)
  • globals(), locals().
  • type(obj)
  • dir(obj) -- Returns interesting names, but list is not necessarily complete.
  • cls.__bases__
  • obj.__class__
  • obj.__doc__
  • obj.__class__.__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.

12   Special Tasks

12.1   File input and output

Create a file object. Use file() (or open() in Jython prior to Jython 2.2). However, open is a factory function, and, according to some, is preferred over file.

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 (Jython 2.2a or later). 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().

12.2   Unit tests

For more information, see 5.3 unittest -- Unit testing framework (http://docs.python.org/lib/module-unittest.html).

Here is a 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:

  • GenTest is our test suite class. It inherits from unittest.TestCase.
  • Each method in GenTest whose name begins with "test" will be run as a test.
  • The tests are run in alphabetic order by method name.
  • Defaults in class TestLoader for the test name prefix and sort comparison function can be overridden. See 5.3.8 TestLoader Objects.
  • A test case class may also implement methods named setUp() and tearDown() to be run before and after tests. See 5.3.5 TestCase Objects. Actually, the first test method in our example should, perhaps, be a setUp() method.
  • The tests use calls such as self.failUnless() to report errors. These are inherited from class TestCase. See 5.3.5 TestCase Objects.
  • Function suite() creates an instance of the test suite.
  • Function test() runs the tests.

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

  • Without unit tests, corner cases may not be checked. This is especially important, since Python does relatively little compile time error checking.
  • Unit tests facilitate a frequent and short design and implement and release development cycle. See ONLamp.com -- Extreme Python and What is XP.
  • Designing the tests before writing the code is "a good idea".

12.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 set: 5.2 doctest -- Test interactive Python examples.

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 python interpreter or an interpreter that produces the same prompts. Note that IPython does not produce those prompts by default, but can be configured to do so. 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 code similar to the following at the bottom of your module:

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

12.4   Installing Python packages

12.4.1   Python packages

Simple:

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

More complex:

  • Look for a README or INSTALL file at the root of the package.

  • Type the following for help:

    $ python setup.py cmd --help
    $ python setup.py --help-commands
    $ python setup.py --help [cmd1 cmd2 ...]
    
  • And, for even more details, see Installing Python Modules.

12.4.2   Jython packages

Some Jython packages will be distributed as a Java jar file. If that is the case, add the jar file somewhere on your classpath.

If the package is distributed as a standard Python package with a setup.py installer file and if there are no C/C++ files in the package, then you might try something like the following:

$ python setup.py install --prefix /path/to/install/directory

And, then put that install directory on your classpath.

13   More Python Features and Exercises

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

14   Installing and Running Jython

14.1   Install Python

If it is not already installed on your system, you are likely to want Python. It is not necessary for running Jython code. But, you are likely to want to do at least some of your work and tests with Python.

14.1.1   MS Windows

You can find the latest Python at http://www.python.org. Currently, the latest is Python 2.4.2.

If you are on MS Windows, you will likely also want to install Python for Windows Extensions.

14.2   Install Jython

You will need Java installed, of course. And, since you are like to want to use Jython class libraries from Jython, it is also likely that you will want the Java SDK. Important: If more than one version of Java is installed on your machine, make sure that when you install Jython using the version of Java for which the SDK is installed and the version of Java that you will be using when you run Jython.

Follow the instructions at http://www.jython.org/install.html.

To install Jython 2.1 from the Java class file:

  1. Download jython_21.class: Download Jython.

  2. Follow the installation instructions: Installing Jython (http://www.jython.org/install.html). You are likely to do something like this:

    $ java -cp . jython_21 -o Jython-2.1
    

    Where java runs the version of Java that you will be using when you run Jython.

Jython 2.1 is the latest stable version. Jython 2.2a is also available, and seems quite usable and stable to me. To install it, first down-load it from http://www.jython.org/. Then use something like the following (depending on the version):

$ java -jar jython_Release_2_2alpha1.jar

Command line editing and command line history -- Note that at the time of this writing, on Linux, the Jython 2.2a interactive shell does not have readline support (command-line editing, command history) built in. (Some versions of Jython 2.1 have it.) But, on UNIX/Linux machines, rlwrap will fill this need. You can get rlwrap here:

In order to build rlwrap, you will need The GNU Readline Library. For Linux, there are likely to be binary installers for rlwrap for specific Linux platform.

Run rlwrap with the following:

$ rlwrap -r path-to-jython/jython

The -r command-line flag gives some word completion (using the TAB key) for previously seen words.

There is also a FAQ entry. Visit Jython FAQ Index (http://www.jython.org/cgi-bin/faqw.py?req=index) and then look for "2.4. Why no command-line history in Jython?". The suggestion to use Demo/swing/Console.py in the Jython distribution is a fairly useful one.

Also, there is JythonConsole (http://don.freeshell.org/jython/), which, in addition to command line history and editing, provides additional features such as code completion and method (tip) information.

For more on consoles and interactive shells for Jython, see the Wiki page: ReadlineSetup (http://wiki.python.org/jython/ReadlineSetup).

14.3   Configuration

There are several places to configure Jython.

14.3.1   Command-line options

To display the options for jython, type:

$ jython --help

And:

$ jythonc --help

14.3.2   Jython configuration files

For explanation of configuration options and values, see:

14.3.3   Checking configuration values

From within the Jython interactive interpreter or from within your Jython application, you can display the values of configuration properties.

To get the system properties as a dictionary-like object, do:

>>> from java.lang import System
>>> props = System.getProperties()

Of particular interest are the following:

  • props['java.class.path'] -- Location of the Jython jar file.
  • props['java.library.path'] -- Locations of Java class libraries.

Other properties are in sys.registry:

>>> import sys
>>> r = sys.registry
>>> for k in r:
...   print k, r[k]

Here is a script that you may find useful when interactively inspecting system properties:

>>> from java.lang import System
>>> props = System.getProperties()
>>> names = []
>>> for name in props.keys():
...     names.append(name)
...
>>> names.sort()    # now you can list the keys in alpha order
>>> for val in props['java.class.path'].split(':'):
...     print val
...
/home/dkuhlman/a1/Python/Jython/Tmp1/Jython-2.1/jython.jar
/usr/share/jython/jython.jar

14.3.4   Classpath and python path

Jython can pick up Java class files from locations on either the Jython/Python path (see sys.path) or the Java classpath. Set these with the following:

  • The Python/Jython path can be set in your registry file. See registry variable python.path.

    Or, at runtime, you could do:

    >>> import sys
    >>> sys.path.append('/path/to/module')
    

    But, you must do the above before trying to import the module.

  • Set the classpath by setting the CLASSPATH environment variable. Note that (on my Linux machine, at least) the CLASSPATH environment variable is picked up and added to the Java -classpath flag.

A few rules about CLASSPATH and python.path:

  • sys.path in the registry file -- Add here to enable importing from Java classes (.java), Java class libraries (.jar), and Jython/Python (.py).
  • CLASSPATH -- Add here to enable importing from Java classes (.java) and Java class libraries (.jar), but not Jython/Python (.py).

14.4   Running Jython

The Jython interactive, command-line interpreter: jython.

Jython IDEs (interactive development environments) -- There is a Jython plug-in for Eclipse. See: http://pydev.sourceforge.net/.

Exercise -- Start the Jython interpreter. Then do each of the following:

  • Print "hello".
  • Define an empty class.
  • Import a Python/Jython file containing a class definition. Create an instance of that class.
  • Import a module from the standard Python/Jython library, for example, re or os.path. Use a method from that module.
  • Import a Java class, for example, java.util.Vector. Create and use an instance of that class.

Running Jython scripts:

  • From the command line, run a script with jython. For example:

    $ jython myscript.py
    
  • For help, run:

    $ jython --help
    
  • For debugging, use something similar to the following:

    import pdb
    pdb.run('main()')
    
    Or::

    import pdb pdb.set_trace()

    For example:

    def main():
        util101()
    
    if __name__ == '__main__':
        import pdb; pdb.set_trace()
        main()
    
  • To "set a breakpoint" in your code so that it will drop into debugger, either (1) use the b command at the pdb prompt or (2) add the following to your code at the location where you wish to drop into the debugger:

    import pdb; pdb.set_trace()
    

    For more information on the Python debugger, see The Python Debugger in the Python standard documentation.

  • To make a script both "run-able" and "import-able", use the following idiom:

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

Don't forget to include a doc string at the top of your module for documentation.

Exercise -- Create a small Jython script:

  • Include a class in your script that creates an instance of java.util.Vector.
  • Make the script both "run-able" and "import-able".
  • From the Jython interpreter, import the script and create an instance of the class.
  • Import pdb, then use it to debug and run your script.
  • From the command line, use jython to run the script.
  • Add pdb debugging to your script. Run the script again from the command line. Step through several lines of code.

14.5   Running jythonc

jythonc is the Jython compiler. It compiles Jython code to Java byte-code for the JVM.

What jythonc does:

  • Generates .java source code files.
  • Compiles: .java --> .class.

The class files generated by jythonc can be used from standard Java. But, must also make the Jython jar file available.

Learn more about jythonc in the section Compiling Jython to and for Java.

15   Calling Java from Jython

15.1   Calling existing Java code

Import the Java module and call functions and objects in it. It works the way you would hope and expect it to. Here is an example:

>>> from java.util import Vector
>>> v = Vector()
>>> dir(v)
['__init__', 'add', 'addAll', 'addElement', 'capacity', 'class', 'clear', 'clone', 'contains', 'containsAll', 'copyInto', 'elementAt', 'elements', 'empty', 'ensureCapacity', 'equals', 'firstElement', 'get', 'getClass', 'hashCode', 'indexOf', 'insertElementAt', 'isEmpty', 'iterator', 'lastElement', 'lastIndexOf', 'listIterator', 'notify', 'notifyAll', 'remove', 'removeAll', 'removeAllElements', 'removeElement', 'removeElementAt', 'retainAll', 'set', 'setElementAt', 'setSize', 'size', 'subList', 'toArray', 'toString', 'trimToSize', 'wait']
>>>
>>> v.add('aaa')
1
>>> v.add('bbb')
1
>>> for val in v:
...     print val
...
aaa
bbb

In some cases you will need to pass Java objects to Java methods.

Special treatment for some overloaded Java methods -- Explicitly create and pass Jython objects.

Often you can use Python/Jython style and idioms to process Java objects. For example: the Jython for statement can be applied to Java collection objects.

Exercise -- Use the class java.util.Hashtable to create a dictionary with several keys and values, then print out the keys and their values. Solution:

>>> from java.util import Hashtable
>>> impl_language = Hashtable()
>>> impl_language.put('jython', 'java')
>>> impl_language.put('python', 'c')
>>> for key in impl_language.keys():
...     print '%s is implemented in %s' % (key, impl_language[key])
...
python is implemented in c
jython is implemented in java

15.2   Preparing Java code to be called from Jython

Another view: Java is the extension language for Jython.

No special work is required. Jython can call normal Java classes.

Need to pay attention to data types, for example, on the Jython side. Use an explicit cast, for example, float(5).

For additional help, see:

15.2.1   A simple class, doc strings, etc

A first, simple example:

// Showme.java

import org.python.core.*;

public class ShowMe
{
    public static PyString __doc__ =
        new PyString("Simple Jython extension #1");

    public String name;

    public ShowMe(String newName)
    {
        name = newName;
    }
    public static PyString __doc__set_name = new PyString(
        "Set the name attribute");
    public void set_name(String newName)
    {
        name = newName;
    }
    public static PyString __doc__get_name = new PyString(
        "Get the name attribute");
    public String get_name()
    {
        return name;
    }

    public static PyString  __doc__Show = new PyString(
        "Show the name attribute");
    public void Show()
    {
        System.out.println("My name is \"" + name + "\".");
    }
}

Notes:

  • Doc strings for the class and methods are defined with public static Strings. You can, alternatively, use PyString.
  • For more complex control over doc strings (for example, in a Java files that contains multiple classes) your class can implement the ClassDictInit interface and implement the classDictInit method. See "Jython for Java Programmers", pp. 276 ff.

15.2.2   Working with Jython arguments

The ArgParser class helps us handle Jython keyword arguments. If helps us support the analog of Jython's *args and **kwargs in Java methods.

How to do it -- And overview:

  1. Define your Java method with the following prototype:

    public PyObject foo(PyObject[] args, String[] keywords);
    
  2. Parse the arguments with class ArgParser.

  3. Access individual arguments with ArgParser methods getInt(), getString(), getList(), and getPyObject().

  4. Since both args and keywords are arrays, check the number of arguments actually passed with args.length and keywords.length.

For more information, see: org.python.core Class ArgParser.

Exercise -- (1) Write a Java class containing a method that prints all its arguments and all the keyword arguments passed to it. (2) Then call that method from Jython.

Solution:

// DemoArgs.java

import org.python.core.*;

public class DemoArgs
{
    public static PyString __doc__ =
        new PyString("Demonstrate the use of complex arguments.");

    public String name;
    public String value;

    public DemoArgs(String newName, String newValue)
    {
        name = newName;
        value = newValue;
    }

    public static PyString __doc__set_name = new PyString(
        "Set the name attribute");
    public void set_name(PyObject[] args, String[] kwargs)
    {
        System.out.println("length(args): " +
            String.valueOf(args.length) +
            " length(kwargs): " +
            String.valueOf(kwargs.length)
            );
        ArgParser ap = new ArgParser("set_name", args, kwargs,
            new String[] {"name", "value"});
        String newName = ap.getString(0, "");
        String newValue = ap.getString(1, "<empty>");
        if (newName.compareTo("") != 0)
        {
            name = newName;
        }
        value = newValue;
    }
    public static PyString __doc__get_name = new PyString(
        "Get the name attribute");
    public String get_name()
    {
        return name;
    }

    public static PyString __doc__get_value = new PyString(
        "Get the value attribute");
    public String get_value()
    {
        return value;
    }

    public static PyString  __doc__Show = new PyString(
        "Show the name and value attributes");
    public void Show()
    {
        System.out.println("My name is \"" + name +
            "\" and my value is \"" + value + "\".");
    }
}

Compile the above file with javac or some other Java compiler. To do so, you will need to add jython.jar to your CLASSPATH.

Notes:

  • Use class ArgParser to capture the arguments.
  • Use ArgParser methods getInt, getString, getPyObject, and getList to retrieve arguments.
  • Notice that in method get_name, we print the length of the args and kwargs. This demonstrates that you can check the length of these arrays and can throw an exception if, for example, too few arguments are passed.

15.2.3   Sub-classing a Java class

Notice that, in Jython, we can extend a class written in Java:

import DemoArgs

class Fancy(DemoArgs):
    def __init__(self, name, value):
        DemoArgs.__init__(self, name, value)
    def ShowFancy(self):
        print "I'm fancy and my name is %s and my value is %s" % \
            (self.name, self.value)

def test():
    f = Fancy('dave', 'funny')
    f.ShowFancy()
    f.set_name('daniel', 'cute')
    f.ShowFancy()

test()

When you run the above, you should see something like the following:

$ jython tmp.py
I'm fancy and my name is dave and my value is funny
length(args): 2 length(kwargs): 0
I'm fancy and my name is daniel and my value is cute

15.2.4   Emulating Jython Dictionaries, Sequences, Etc.

Extend class org.python.core.PyObject and its sub-classes. See: org.python.core Class PyObject.

Implement the following methods:

__getitem__()
__finditem()
__setitem__()
__delitem__()
...

getitem() vs. finditem():

  • If the index is not found or out of range, finditem() returns null, whereas __getitem() should throw an exception.
  • The Jython API documentation says to override finditem() and not getitem(). See: org.python.core Class PyObject.

See 3.3.5 Emulating container types in the Python Reference Manual for more information on customizing dictionaries and sequences.

Exercise -- (1) Write a Java class that emulates or imitates a Jython dictionary. (2) In addition, each access method should print a message. (3) Test your Java class from Jython by creating an instance of it, then setting and retrieving a key-value pair.

Solution #1 -- This solution is for educational purposes only (see solution #2):

// TestDict.java

import org.python.core.*;
import java.util.*;

public class TestDict
{
    public Hashtable data;

    public TestDict()
    {
            data = new Hashtable();
    }
    public void __setitem__(String key, String value)
    {
            data.put(key, value);
            System.out.println("Added key \"" + key + "\"  value: \"" +
                                               value + "\"");
    }
    public String __getitem__(String key)
    {
            if (data.containsKey(key))
            {
                    String value = (String)data.get(key);
                    System.out.println("Found key \"" + key + "\"  value: \"" +
                                               value + "\"");
                    return value;
            }
            else
            {
                    throw new PyException(Py.KeyError, "The key does not exit.");
            }
    }
    public boolean __contains__(String key)
    {
            if (data.containsKey(key))
            {
                    System.out.println("Found key \"" + key + "\"");
                    return true;
            }
            else
            {
                    System.out.println("Did not find key \"" + key + "\"");
                    return false;
            }
    }
}

Notes:

  • The above class implements a limited part of the Jython dictionary protocol, in particular __setitem__, __getitem__, and __contains__.

  • This above solution also illustrates how to throw ("raise" in Jython terms) an exception from Java that can be caught in Jython. Here is an example of catching that exception on the Jython side:

    >>> try:
    ...   x = b['xyz']
    ... except KeyError, e:
    ...   print '*** error: %s' % e
    ...
    *** error: The key does not exit.
    

Solution #2 -- This solution shows how you most likely would start if you wanted to extend the dictionary type or implement a custom dictionary type:

// TestDictSub.java

import org.python.core.*;
import java.util.*;

public class TestDictSub extends PyDictionary
{
    public void __setitem__(PyObject key, PyObject value)
    {
        super.__setitem__(key, value);
        System.out.println("Added key \"" + key + "\"  value: \"" +
                           value + "\"");
    }
    public PyObject __getitem__(PyObject key)
    {
        if (super.has_key(key))
        {
            PyObject value = super.__getitem__(key);
            System.out.println("Found key \"" + key + "\"  value: \"" +
                           value + "\"");
            return value;
        }
        else
        {
            throw new PyException(Py.KeyError, "The key does not exit.");
        }
    }
}

Notes:

  • This class inherits the methods in the PyDictionary class. It overrides several of those methods, specifically __setitem__ and __getitem__.
  • The Java class could also extend the dictionary type by implementing additional, new methods.

15.2.5   Emulating Jython object attribute access

We can implement and override object attribute access in a Java class:

Extend class org.python.core.PyObject and its sub-classes.

Implement the following methods:

__findattr__()
__setattr__()
__delattr__()

__findattr__() is called only if an attribute is not found in an object.

Exercise -- (1) Write a Java class class that supports access to attributes. (2) In addition, each access method should print a message. (3) Test your Java class from Jython by creating an instance of it, then setting and getting an attribute.

Solution:

// TestDictSub.java

import org.python.core.*;
import java.util.*;

public class TestDictAttr extends PyDictionary
{
    public PyObject __findattr__(String key)
    {
        PyString objkey = new PyString(key);
        if (super.has_key(objkey))
        {
            PyObject value = super.__getitem__(objkey);
            System.out.println("Found attr \"" + key + "\"  value: \"" +
                           value + "\"");
            return value;
        }
        else
        {
            throw new PyException(Py.KeyError, "The attr does not exit.");
        }
    }
}

Notes:

  • Test this solution with the following:

    $ rlwrap -r ./jython
    Jython 2.2a1 on java1.4.2 (JIT: null)
    Type "copyright", "credits" or "license" for more information.
    >>>
    >>> import TestDictAttr
    >>> a = TestDictAttr()
    >>> print a.dave
    Traceback (innermost last):
      File "<console>", line 1, in ?
    KeyError: The attr does not exit.
    >>> a['dave'] = 'some little value'
    >>> print a.dave
    Found attr "dave"  value: "some little value"
    some little value
    
  • Arguments to __findattr__ and __finditem__ must be interned strings. Literal strings are automatically interned. For other strings, use intern(s).

16   Compiling Jython to and for Java

Another view: Jython is the extension language for Java.

Use jythonc.

You can extend Java classes.

You can add (J)Python protocols to Java classes.

You will need to describe the signature of methods in order to make them callable from Java (in addition to Jython).

What jythonc does -- jythonc translates .py files into .java source code files, then compiles these to .class files.

With jythonc, you can also:

Java compatible classes - In order to implement a Java compatible class (that is, one that acts like a native Java class and can be called from Java), your Jython code must follow these rules:

How to use jythonc:

Some notes:

Example -- The following Jython code extends a Java class. Compile it with jythonc:

# Foo.py

import java

class Foo(java.util.Date):
    def __init__(self):
        self.count = 0
    def bar(self, incr=1):
        """@sig void bar(int incr)"""
        self.count += incr
        return self.count
    def toString(self):
        cnt = self.bar()
        return "Foo[" + java.util.Date.toString(self) + " " + `cnt` + "]"

Example, continued -- Here is Java code to test the above. Compile it with javac and run it:

// FooTest.java

import Foo;

public class FooTest {
     public static void main(String[] args) {
         Foo foo = new Foo();
         System.out.println(foo);
         foo.bar();
         foo.bar(43);
         System.out.println(foo);
     }
}

Notes:

In order to implement a Java compatible class (that is, one that acts like a native Java class and can be called from Java), your Jython code must follow these rules:

Here is another simple example:

"""simpleclass.py

This is a simple class to demonstrate the use of jythonc.
"""

import java.lang.Object

class simpleclass(java.lang.Object):
    def __init__(self, name='The Horse With No Name'):
        """public simpleclass(String name)
        """
        self.name = name
        self.size = -1
    def set_name(self, name):
        """@sig public void set_name(String name)
        """
        self.name = name
    def set_size(self, size):
        """@sig public void set_size(int size)
        """
        self.size = size
    def show(self):
        """@sig public String show()
        """
        return 'name: %s  size: %s' % (self.name, self.size, )

And, a Java test harness for this simple example:

// simpleclasstest.java

import simpleclass;

public class simpleclasstest {
    public static void main(String[] args) {
    String s1;
    simpleclass sc = new simpleclass();
    s1 = sc.show();
    System.out.println("1. " + s1);
    sc.set_name("dave");
    sc.set_size(4321);
    s1 = sc.show();
    System.out.println("2. " + s1);
    }
}

Notes:

Put jpywork on your CLASSPATH, then use the following to compile and test the above:

$ jythonc simpleclass.py
$ javac simpleclasstest.java
$ java simpleclasstest
1. name: The Horse With No Name  size: -1
2. name: dave  size: 4321

In the following example, we create a stand-alone Jar file, that is, one that can be executed as a script on a machine where Jython is not installed. Here is the Jython script:

# test_jythonc.py

import sys

def test(words):
    msgs = ['hi', 'bye']
    for word in words:
        msgs.append(word)
    for msg in msgs:
        print msg

def main():
    args = sys.argv[1:]
    test(args)

if __name__ == '__main__':
    main()

Compile and build a Jar file with the following:

$ jythonc --all --jar mytest.jar test_jythonc.py

Run it as follows:

$ java -jar mytest.jar hello goodbye
hi
bye
hello
goodbye

Notes:

16.1   Calling Jython Code from Jython

From Jython, you can run Jython and Python code. When you do so, you may run Java code that is in a super-class or is used by the Jython code.

But, notice that, from Jython, you cannot call Python code that has been extended with C.

16.2   Calling Jython Code from Java

Must compile Jython/Python to Java with jythonc.

Must pay attention to method signatures. Define method signature in Jython in a doc string with @sig. Then look at the generated .java file.

Other things to be aware of:

  • Must set classpath to include jpywork.
  • Must write a Java compatible class. See above.

16.3   Another example -- Jython-2.2a/Demo/javaclasses

What this example shows:

  • How to write a class that can be compiled (with jythonc) and then called from Java.
  • How to write method signatures for Jython methods.
  • How to compile the the Jython code and the Java code.

For example, I compiled and ran the example in Jython-2.2a/Demo/javaclasses with the following:

$ rm -rf jpywork/
$ ../../jythonc --package pygraph Graph.py
$ javac -classpath .:../../jython.jar pygraph/PythonGraph.java
$ java -classpath .:../../jython.jar:jpywork pygraph.PythonGraph

For more information, see Jython-2.2a/Demo/javaclasses/readme.txt.

17   Embedding the Jython Interpreter

17.1   It's simple

Embedding the Jython interpreter can be as simple as this:

// File: SimpleEmbedded.java
import org.python.util.PythonInterpreter;
import org.python.core.*;
import java.io.*;

public class SimpleEmbedded
{
    public static void main (String[]args) throws PyException, IOException
    {
        BufferedReader terminal;
        PythonInterpreter interp;
        terminal = new BufferedReader (new InputStreamReader (System.in));
        System.out.println ("Hello");
        interp = new PythonInterpreter ();
        interp.exec ("import sys");
        interp.exec ("print sys");
        interp.set ("a", new PyInteger (42));
        interp.exec ("print a");
        interp.exec ("x = 2+2");
        PyObject x = interp.get ("x");
        System.out.println ("x: " + x);
        PyObject localvars = interp.getLocals ();
        interp.set ("localvars", localvars);
        String codeString = "";
        String prompt = ">> ";
        while (true)
        {
            System.out.print (prompt);
            try
            {
                codeString = terminal.readLine ();
                if (codeString.compareTo ("exit") == 0)
                {
                    System.exit (0);
                    break;
                }
                interp.exec (codeString);
            }
            catch (IOException e)
            {
                e.printStackTrace ();
            }
        }
        System.out.println ("Goodbye");
    }
}

17.2   But, there are a few complexities

You will want to selectively expose capabilities in your application to scripts run by/on the embedded Jython interpreter.

You will want to protect your application from malicious or erroneous scripts.

Here are a few suggestions:

  • Describe your possible classes of users (those who will write scripts) with respect to (1) trusted vs. untrusted and (2) error tolerant vs. non-tolerant.
  • For users who are trusted and error tolerant, provide transparent objects from your application.
  • For users who are trusted and not error tolerant, provide opaque objects, i.e. wrappers for real objects from your application.
  • For users who are not trusted, implement a security policy, or do not expose a scripting interface at all.

17.3   Exposing transparent objects

Java application objects and values can be passed through to scripts executed or evaluated by the embedded interpreter.

Some mechanisms for passing objects:

  • set and get -- Use these to set or retrieve values in the local namespace for the scripts that your embedded interpreter will run or has run.
  • setLocals and getLocals -- Using these methods, you can pass or retrieve the entire namespace. If you are inserting values to be used (or shared) by scripts, you may want to retrieve and, possibly, copy the initial namespace. Remember that is a Jython dictionary, so modifying it without copying may affect other scripts running in the same interpreter.

17.4   Exposing opaque objects

This is similar to the strategy for transparent objects, except that you must implement wrapper classes, then provide instances of these classes instead of instances of transparent objects.

17.5   Type conversion

Mostly, Jython takes care of this for you.

However, at times it may help to know what conversions are performed.

And, you can also perform explicit conversions.

18   Embedding and Extending -- A Summary

Here is what we have learned to do:

19   Advanced Topics

19.1   Event handling

Events are easy in Jython.

Here is an example taken from "An Introduction to Jython" (http://www.javalobby.org/articles/jython/):

from javax.swing import *

def hello(event):
    print "Hello.  I'm an event."

def test():
    frame = JFrame("Hello Jython")
    button = JButton("Hello", actionPerformed = hello)
    frame.add(button)
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
    frame.setSize(300, 300)
    frame.show()

test()

19.2   XML

19.2.1   jaxp

Note: Tested with jython-2.2a.

Example:

"""

To run this example, set your CLASSPATH with something like the following:

    export CLASSPATH=\
    ${path-to-xerces}/xerces-2_8_0/xercesImpl.jar:\
    ${path-to-xerces}/xerces-2_8_0/xml-apis.jar


"""


import sys
import java.lang.Boolean
from javax.xml.parsers import DocumentBuilderFactory


def test(infilename):
    """Parse XML document and show attributes and names.
    """
    dbf = DocumentBuilderFactory.newInstance()
    t = java.lang.Boolean(1)
    dbf.setNamespaceAware(t)
    db = dbf.newDocumentBuilder();
    doc = db.parse(infilename)
    # print dir(doc)
    node = doc.getDocumentElement()
    print 'Attributes:'
    show_attrs(node)
    print 'Names:'
    show_names(node)


def show_attrs(node):
    """Show the attributes and their values.
    """
    node = node.getFirstChild()
    while node:
        if node.getNodeType() == node.ELEMENT_NODE:
            print '   %s:' % (node.getTagName(), )
            attrs = node.getAttributes()
            count = attrs.getLength()
            for idx in range(count):
                attr = attrs.item(idx)
                print '        %s: %s' % (
                    attr.getNodeName(), attr.getNodeValue(), )
        node = node.getNextSibling()


def show_names(node):
    """Show the value of the name element for each person element.
    """
    node = node.getFirstChild()
    while node:
        if (node.getNodeType() == node.ELEMENT_NODE and
            node.getTagName() == 'person'):
                show_person_name(node)
        node = node.getNextSibling()


def show_person_name(node):
    node = node.getFirstChild()
    while node:
        if (node.getNodeType() == node.ELEMENT_NODE and
            node.getTagName() == 'name'):
                show_text('name: ', node)
        node = node.getNextSibling()


def show_text(msg, node):
    """Show a message and the value of a text node.
    """
    node = node.getFirstChild()
    while node:
        if node.getNodeType() == node.TEXT_NODE:
            print '    %s %s' % (msg, node.getNodeValue(), )
        node = node.getNextSibling()


def usage():
    print 'Usage: jython test_jaxp.py  <infilename>'
    sys.exit(-1)


def main():
    args = sys.argv[1:]
    if len(args) != 1:
        usage()
    test(args[0])


if __name__ == '__main__':
    main()

Resources:

19.2.2   Xerces

Xerces is an implementation of XML parsers and a lot more. The JAXP API is also implemented in Xerces2.

Obtain Xerces here: http://xerces.apache.org/xerces2-j/download.cgi.

Installation instructions are here: Installation Instructions.

Set-up -- Set your CLASSPATH. After unpacking the Xerces distribution, add the following jar files to your CLASSPATH:

  • xercesImpl.jar
  • xml-apis.jar

Here is an example that uses the Xerces DOM parser to parse an XML document, then print out information about the top level nodes in the document:

from org.apache.xerces.parsers import DOMParser as dp

def test():
    parser = dp()
    parser.parse('people.xml')
    doc = parser.getDocument()
    node = doc.getFirstChild()
    node = node.getFirstChild()
    while node:
        if node.getNodeType() == node.ELEMENT_NODE:
            print node.getTagName()
            attrs = node.getAttributes()
            count = attrs.getLength()
            for idx in range(count):
                attr = attrs.item(idx)
                print '    %s: %s' % (attr.getNodeName(), attr.getNodeValue(),)
        node = node.getNextSibling()

if __name__ == '__main__':
    test()

Here is another example. This one also prints out the text values of the name elements:

"""

To run this example, set your CLASSPATH with something like the following:

    export CLASSPATH=\
    ${path-to-jython2.2a}/jython.jar:\
    ${path-to-xerces}/xerces-2_8_0/xercesImpl.jar:\
    ${path-to-xerces}/xerces-2_8_0/xml-apis.jar


"""


import sys
from org.apache.xerces.parsers import DOMParser as dp


def test(infilename):
    """Parse XML document and show attributes and names.
    """
    parser = dp()
    parser.parse(infilename)
    doc = parser.getDocument()
    node = doc.getFirstChild()
    print 'Attributes:'
    show_attrs(node)
    print 'Names:'
    show_names(node)


def show_attrs(node):
    """Show the attributes and their values.
    """
    node = node.getFirstChild()
    while node:
        if node.getNodeType() == node.ELEMENT_NODE:
            print '   %s:' % (node.getTagName(), )
            attrs = node.getAttributes()
            count = attrs.getLength()
            for idx in range(count):
                attr = attrs.item(idx)
                print '        %s: %s' % (
                    attr.getNodeName(), attr.getNodeValue(), )
        node = node.getNextSibling()


def show_names(node):
    """Show the value of the name element for each person element.
    """
    node = node.getFirstChild()
    while node:
        if (node.getNodeType() == node.ELEMENT_NODE and
            node.getTagName() == 'person'):
                show_person_name(node)
        node = node.getNextSibling()


def show_person_name(node):
    node = node.getFirstChild()
    while node:
        if (node.getNodeType() == node.ELEMENT_NODE and
            node.getTagName() == 'name'):
                show_text('name: ', node)
        node = node.getNextSibling()


def show_text(msg, node):
    """Show a message and the value of a text node.
    """
    node = node.getFirstChild()
    while node:
        if node.getNodeType() == node.TEXT_NODE:
            print '    %s %s' % (msg, node.getNodeValue(), )
        node = node.getNextSibling()


def usage():
    print 'Usage: jython test_xerces.py  <infilename>'
    sys.exit(-1)


def main():
    args = sys.argv[1:]
    if len(args) != 1:
        usage()
    test(args[0])


if __name__ == '__main__':
    main()

Notes:

  • Except for the parser set-up (in function test), this example is almost the same as the JAXP example. For the most part, it uses the same API.

Resources:

19.2.3   dom4j

Example:

"""

To run this example, add the following to your CLASSPATH:

    ${path-to-dom4j}/dom4j-1.6.1.jar


"""


import sys
from org.dom4j.io import SAXReader

def show_indent(level):
    return '    ' * level

def show_node(node, level):
    """Display one node in the DOM tree.
    """
    if node.getNodeType() == node.ELEMENT_NODE:
        name = node.getName()
        print '%sNode: %s' % (show_indent(level), name, )
        attrs = node.attributes()
        for attr in attrs:
            aName = attr.getQualifiedName()
            aValue = attr.getValue()
            print '  %sAttr -- %s: %s' % (show_indent(level), aName, aValue,)
    if node.getName() == 'interest':
        val = node.getText()
        print '%sinterest: "%s"' % (show_indent(level+1), val, )
    elif node.getName() == 'name':
        val = node.getText()
        print '%sname    : "%s"' % (show_indent(level+1), val, )
    #
    # Note that there are *no* TEXT_NODE's.
    # dom4j does not seem to produce any.
    #
    if node.getNodeType() == node.TEXT_NODE:
        print '**** text node'


def show_tree(node, level):
    show_node(node, level)
    level1 = level + 1
    children = node.elements()
    for child in children:
        show_tree(child, level1)

def test():
    print 'Version: %s' % (sys.version, )
    reader = SAXReader()
    doc = reader.read('file:///home/dkuhlman/a1/Python/Jython/Test/people.xml')
    root = doc.getRootElement()
    show_tree(root, 0)

def main():
    test()

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

Resources:

19.3   Database access

19.3.1   JDBC

JDBC is Java classes. It is, therefore, usable from Jython.

You will need JDBC driver/adapters for your database.

But, JDBC is not very Pythonic.

19.3.2   zxJDBC

zxJDBC is Pythonic. zxJDBC implements the Python DB API on top of JDBC. For more on the Python DB API, see SIG on Tabular Databases in Python and Python Database API Specification v2.0.

If zxJDBC is not already in your installed version of Jython, then you can:

  1. Downloading the source from http://sourceforge.net/projects/zxjdbc.
  2. Creating a directory (e.g. zxJDBC), then un-rolling it.
  3. Add zxJDBC/lib/zxJDBC.jar to your CLASSPATH

You can get documentation on zxJDBC by:

  1. Downloading the source from http://sourceforge.net/projects/zxjdbc.
  2. Creating a directory (e.g. zxJDBC), then un-rolling it.
  3. Pointing your browser at zxJDBC/doc/index.html.

Example -- The following example opens a connection to a PostgreSQL database, then prints out the rows in a table in that database. In order to make this example work, I put the following jar files on my CLASSPATH:

  • zxJDBC.jar -- Not needed for Jython 2.2, and possibly not needed for the version of Jython 2.1 on your machine. JDBC support has been folded into Jython 2.1 and Jython 2.2a.
  • postgresql-8.1-407.jdbc3.jar -- You will need a suitable driver for your database and version.

Here is the example implementation:

"""

For this test, add the JDBC driver to your CLASSPATH.  For example,
in my case I added:

    postgresql-8.1-407.jdbc3.jar

"""

from com.ziclix.python.sql import zxJDBC

def test():
    d, u, p, v = (
        "jdbc:postgresql://thrush:5432/test",   # ... host, port, database
        "postgres",                             # user name
        "mypassword",                           # pass word
        "org.postgresql.Driver",                # driver
        )
    db = zxJDBC.connect(d, u, p, v, CHARSET='iso_1')
    cur = db.cursor()
    cur.execute('select * from plant_db')
    rows = cur.fetchall()
    s1 = '%s %s %s' % (
        'Name'.ljust(12),
        'Description'.ljust(24),
        'Rating'.ljust(10),
        )
    print s1
    s1 = '%s %s %s' % (
        '===='.ljust(12),
        '==========='.ljust(24),
        '======'.ljust(10),
        )
    print s1
    for row in rows:
        rating = str(row[2])
        print '%s %s %s' % (
            row[0].ljust(12), row[1].ljust(24), rating.ljust(10), )
    cur.close()
    db.close()

if __name__ == '__main__':
    test()

Which, when connected to my trivial, little database, prints out the following:

Name         Description              Rating
====         ===========              ======
tomato       red and tasty            8
peach        sweet and succulent      8
tangerine    sweet but tart           7

Resources:

20   Additional Exercises

[To be added.]

21   References and Sources

Introductory articles: