Monday, July 6, 2009

Book Review: IronPython in Action



IronPython in Action by Michael Foord and Christian Muirhead covers the version of Python built to run on Microsoft's CLR and explains how to use it with the .NET framework.

Disclaimer: I received a review copy of this book from Manning through the PyATL Book Club.

There are two target audiences for this book: experienced Python developers wanting to learn .NET, and experienced .NET developers wanting to learn Python. Both groups will find plenty of interesting material and learn a lot. After some relatively basic introductory chapters, the authors dig right in building a complex GUI application, and then implementing a web interface for the same desktop application.

Along the way they introduce topics such as different programming models in Python, navigating the MSDN documentation (very important for understanding the scope of features available in .NET), packaging your app for distribution under Windows, data persistence, XML parsing, design patterns, automated testing, system administration, relational databases, and two separate GUI libraries.

All of the code is clear, concise, and useful -- there are no fluffy, throw-away code snippets that fall short in the real world. While many of the examples given are specific to IronPython or .NET, the techniques being illustrated are definitely not.

I recommend this book for any Windows developer interested in learning about Python, and for Python developers looking into deploying an application under Windows. If you don't fall into either of those groups, I can still recommend that you pick up a copy for some excellent advice on general programming topics and the solid example code.

Sunday, July 5, 2009

New command line interface to PyMOTW

The 1.95 release of PyMOTW includes a command line interface to access the documentation for a module.

The package can be installed via easy_install or pip:

$ pip install PyMOTW
Downloading/unpacking PyMOTW
Downloading PyMOTW-1.95.tar.gz (2.2Mb): 2.2Mb downloaded
Running setup.py egg_info for package PyMOTW
warning: no files found matching 'ChangeLog'
warning: no files found matching '*.py' under directory 'sphinx/templates'
no previously-included directories found matching 'utils'
Installing collected packages: PyMOTW
Running setup.py install for PyMOTW
changing mode of build/scripts-2.6/motw from 644 to 755
warning: no files found matching 'ChangeLog'
warning: no files found matching '*.py' under directory 'sphinx/templates'
no previously-included directories found matching 'utils'
changing mode of /Users/dhellmann/.virtualenvs/testpymotw/bin/motw to 755
Successfully installed PyMOTW


and then to use the command line interface, run motw.

$ motw -h
Usage: motw [options]

Options:
-h, --help show this help message and exit
-t, --text Print plain-text version of help to stdout
-w, --web Open HTML version of help from web
--html Open HTML version of help from installed file


For example, motw abc opens the local version of this week's article. You can also use the "-w" option to go to my web site instead of reading the local version, so you always have the latest version of an article.

PyMOTW: abc - Abstract Base Classes

abc – Abstract Base Classes

Purpose:Define and use abstract base classes for API checks in your code.
Python Version:2.6

Why use Abstract Base Classes?

Abstract base classes are a form of interface checking more strict than individual hasattr() checks for particular methods. By defining an abstract base class, you can define a common API for a set of subclasses. This capability is especially useful in situations where a third-party is going to provide implementations, such as with plugins to an application, but can also aid you when working on a large team or with a large code-base where keeping all classes in your head at the same time is difficult or not possible.

How ABCs Work

abc works by marking methods of the base class as abstract, and then registering concrete classes as implementations of the abstract base. If your code requires a particular API, you can use issubclass() or isinstance() to check an object against the abstract class.

Let’s start by defining an abstract base class to represent the API of a set of plugins for saving and loading data.

import abc

class PluginBase(object):
__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def load(self, input):
"""Retrieve data from the input source and return an object."""
return

@abc.abstractmethod
def save(self, output, data):
"""Save the data object to the output."""
return

Registering a Concrete Class

There are two ways to indicate that a concrete class implements an abstract: register the class with the abc or subclass directly from the abc.

import abc
from abc_base import PluginBase

class RegisteredImplementation(object):

def load(self, input):
return input.read()

def save(self, output, data):
return output.write(data)

PluginBase.register(RegisteredImplementation)

if __name__ == '__main__':
print 'Subclass:', issubclass(RegisteredImplementation, PluginBase)
print 'Instance:', isinstance(RegisteredImplementation(), PluginBase)

In this example the PluginImplementation is not derived from PluginBase, but is registered as implementing the PluginBase API.

$ python abc_register.py
Subclass: True
Instance: True

Implementation Through Subclassing

By subclassing directly from the base, we can avoid the need to register the class explicitly.

import abc
from abc_base import PluginBase

class SubclassImplementation(PluginBase):

def load(self, input):
return input.read()

def save(self, output, data):
return output.write(data)

if __name__ == '__main__':
print 'Subclass:', issubclass(SubclassImplementation, PluginBase)
print 'Instance:', isinstance(SubclassImplementation(), PluginBase)

In this case the normal Python class management is used to recognize PluginImplementation as implementing the abstract PluginBase.

$ python abc_subclass.py
Subclass: True
Instance: True

A side-effect of using direct subclassing is it is possible to find all of the implementations of your plugin by asking the base class for the list of known classes derived from it (this is not an abc feature, all classes can do this).

import abc
from abc_base import PluginBase
import abc_subclass
import abc_register

for sc in PluginBase.__subclasses__():
print sc.__name__

Notice that even though abc_register is imported, RegisteredImplementation is not among the list of subclasses because it is not actually derived from the base.

$ python abc_find_subclasses.py
SubclassImplementation

Dr. André Roberge has described using this capability to discover plugins by importing all of the modules in a directory dynamically and then looking at the subclass list to find the implementation classes.

Incomplete Implementations

Another benefit of subclassing directly from your abstract base class is that the subclass cannot be instantiated unless it fully implements the abstract portion of the API. This can keep half-baked implementations from triggering unexpected errors at runtime.

import abc
from abc_base import PluginBase

class IncompleteImplementation(PluginBase):

def save(self, output, data):
return output.write(data)

PluginBase.register(IncompleteImplementation)

if __name__ == '__main__':
print 'Subclass:', issubclass(IncompleteImplementation, PluginBase)
print 'Instance:', isinstance(IncompleteImplementation(), PluginBase)
$ python abc_incomplete.py
Subclass: True
Instance:
Traceback (most recent call last):
File "abc_incomplete.py", line 22, in <module>
print 'Instance:', isinstance(IncompleteImplementation(), PluginBase)
TypeError: Can't instantiate abstract class IncompleteImplementation with abstract methods load

Concrete Methods in ABCs

Although a concrete class must provide an implementation of an abstract methods, the abstract base class can also provide an implementation that can be invoked via super(). This lets you re-use common logic by placing it in the base class, but force subclasses to provide an overriding method with (potentially) custom logic.

import abc
from cStringIO import StringIO

class ABCWithConcreteImplementation(object):
__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def retrieve_values(self, input):
print 'base class reading data'
return input.read()

class ConcreteOverride(ABCWithConcreteImplementation):

def retrieve_values(self, input):
base_data = super(ConcreteOverride, self).retrieve_values(input)
print 'subclass sorting data'
response = sorted(base_data.splitlines())
return response

input = StringIO("""line one
line two
line three
""")

reader = ConcreteOverride()
print reader.retrieve_values(input)
print

Since ABCWithConcreteImplementation is an abstract base class, it isn’t possible to instantiate it to use it directly. Subclasses must provide an override for retrieve_values(), and in this case the concrete class massages the data before returning it at all.

$ python abc_concrete_method.py
base class reading data
subclass sorting data
['line one', 'line three', 'line two']

Abstract Properties

If your API specification includes attributes in addition to methods, you can require the attributes in concrete classes by defining them with @abstractproperty.

import abc

class Base(object):
__metaclass__ = abc.ABCMeta

@abc.abstractproperty
def value(self):
return 'Should never get here'


class Implementation(Base):

@property
def value(self):
return 'concrete property'


try:
b = Base()
print 'Base.value:', b.value
except Exception, err:
print 'ERROR:', str(err)

i = Implementation()
print 'Implementation.value:', i.value

The Base class in the example cannot be instantiated because it has only an abstract version of the property getter method.

$ python abc_abstractproperty.py
ERROR: Can't instantiate abstract class Base with abstract methods value
Implementation.value: concrete property

You can also define abstract read/write properties.

import abc

class Base(object):
__metaclass__ = abc.ABCMeta

def value_getter(self):
return 'Should never see this'

def value_setter(self, newvalue):
return

value = abc.abstractproperty(value_getter, value_setter)


class PartialImplementation(Base):

@abc.abstractproperty
def value(self):
return 'Read-only'


class Implementation(Base):

_value = 'Default value'

def value_getter(self):
return self._value

def value_setter(self, newvalue):
self._value = newvalue

value = property(value_getter, value_setter)


try:
b = Base()
print 'Base.value:', b.value
except Exception, err:
print 'ERROR:', str(err)

try:
p = PartialImplementation()
print 'PartialImplementation.value:', p.value
except Exception, err:
print 'ERROR:', str(err)

i = Implementation()
print 'Implementation.value:', i.value

i.value = 'New value'
print 'Changed value:', i.value

Notice that the concrete property must be defined the same way as the abstract property. Trying to override a read/write property in PartialImplementation with one that is read-only does not work.

$ python abc_abstractproperty_rw.py
ERROR: Can't instantiate abstract class Base with abstract methods value
ERROR: Can't instantiate abstract class PartialImplementation with abstract methods value
Implementation.value: Default value
Changed value: New value

Unfortunately, the decorator syntax does not work for read/write abstract properties the way it does with concrete properties.

import abc

class Base(object):
__metaclass__ = abc.ABCMeta

@abc.abstractproperty
def value(self):
return 'Should never see this'

@value.setter
def value_setter(self, newvalue):
return


class Implementation(Base):

_value = 'Default value'

@property
def value(self):
return self._value

@value.setter
def value_setter(self, newvalue):
self._value = newvalue


i = Implementation()
print 'Implementation.value:', i.value

i.value = 'New value'
print 'Changed value:', i.value

Notice that the caller cannot set the property value.

$ python abc_abstractproperty_rw_deco.py
Implementation.value: Default value
Traceback (most recent call last):
File "abc_abstractproperty_rw_deco.py", line 40, in <module>
i.value = 'New value'
AttributeError: can't set attribute

Collection Types

The collections module defines several abstract base classes related to container (and containable) types.

General container classes:

  • Container
  • Sized

Iterator and Sequence classes:

  • Iterable
  • Iterator
  • Sequence
  • MutableSequence

Unique values:

  • Hashable
  • Set
  • MutableSet

Mappings:

  • Mapping
  • MutableMapping
  • MappingView
  • KeysView
  • ItemsView
  • ValuesView

Miscelaneous:

  • Callable

In addition to serving as detailed real-world examples of abstract base classes, Python’s built-in types are automatically registered to these classes when you import collections. This means you can safely use isinstance() to check parameters in your code to ensure that they support the API you need. The base classes can also be used to define your own collection types, since many of them provide concrete implementations of the internals and only need a few methods overridden. Refer to the standard library docs for collections for more details.

See also

abc
The standard library documentation for this module.
PEP 3119
Introducing Abstract Base Classes
collections
The collections module includes abstract base classes for several collection types.
collections
The standard library documentation for collections.
PEP 3141
A Type Hierarchy for Numbers
Wikipedia: Strategy Pattern
Description and examples of the strategy pattern.
Plugins and monkeypatching
PyCon 2009 presentation by Dr. André Roberge

PyMOTW Home

The canonical version of this article

Saturday, July 4, 2009

Book Review: Hello, World!



Hello, World! Computer Programming for Kids and Other Beginners by Warren and Carter Sande is an introduction to programming in general (and Python specifically) aimed at pre-teens or young teens.

Disclaimer: I received a review copy of this book from Manning through the PyATL Book Club.

Although the book is designed for a young audience, it is not condescending as many kids books tend to be so it remains readable by adults who need a very basic text on how computer programs work. And by "basic" I mean from the ground up. The book covers using an editor to create and modify program files, numbers, strings, variables, branching, and looping. It doesn't stop with basic topics, though. By the mid-point of the book, the authors have built up to the point where introducing PyGame and graphics programming isn't a stretch, and by the end of the book they have covered the GUI, animation, and sound techniques needed to create two simple computer games.

The writing style is clear and friendly without coming off as cutesy. Each chapter is relatively short, with review questions at the end in the style of a text book (the answer guide is available in the appendix). There is a liberal use of sidebars to break up longer sections or highlight related digressions. And the authors also don't shy away from showing "broken" versions of programs as they evolve, which teaches the reader how to understand error messages and debug problems -- an extremely important skill for a programmer.

I recommend checking out Hello, World! if you have a young person in your life who is interested in learning about programming. Writing the book was a father/son project, and reading it together seems like a fun parent/child activity for the summer.

Sunday, June 28, 2009

PyMOTW: pyclbr

pyclbr – Python class browser support

Purpose:Implements an API suitable for use in a source code editor for making a class browser.
Python Version:1.4 and later

pyclbr can scan Python source to find classes and stand-alone functions. The information about class, method, and function names and line numbers is gathered using tokenize without importing the code.

The examples below use this source file as input:

"""Example source for pyclbr.
"""

class Base(object):
"""This is the base class.
"""

def method1(self):
return

class Sub1(Base):
"""This is the first subclass.
"""

class Sub2(Base):
"""This is the second subclass.
"""

class Mixin:
"""A mixin class.
"""

def method2(self):
return

class MixinUser(Sub2, Mixin):
"""Overrides method1 and method2
"""

def method1(self):
return

def method2(self):
return

def method3(self):
return

def my_function():
"""Stand-alone function.
"""
return

Scanning for Classes

There are two public functions exposed by pyclbr. readmodule() takes the name of the module as argument returns a mapping of class names to Class objects containing the meta-data about the class source.

import pyclbr
import os
from operator import itemgetter

def show_class(name, class_data):
print 'Class:', name
print '\tFile: {0} [{1}]'.format(os.path.basename(class_data.file), class_data.lineno)
show_super_classes(name, class_data)
show_methods(name, class_data)
print
return

def show_methods(class_name, class_data):
for name, lineno in sorted(class_data.methods.items(), key=itemgetter(1)):
print '\tMethod: {0} [{1}]'.format(name, lineno)
return

def show_super_classes(name, class_data):
super_class_names = []
for super_class in class_data.super:
if super_class == 'object':
continue
if isinstance(super_class, basestring):
super_class_names.append(super_class)
else:
super_class_names.append(super_class.name)
if super_class_names:
print '\tSuper classes:', super_class_names
return

example_data = pyclbr.readmodule('pyclbr_example')

for name, class_data in sorted(example_data.items(), key=lambda x:x[1].lineno):
show_class(name, class_data)

The meta-data for the class includes the file and line number where it is defined, as well as the names of super classes. The methods of the class are saved as a mapping between method name and line number. The output below shows the classes and methods listed in order based on their line number in the source file.

$ python pyclbr_readmodule.py
Class: Base
File: pyclbr_example.py [10]
Method: method1 [14]

Class: Sub1
File: pyclbr_example.py [17]
Super classes: ['Base']

Class: Sub2
File: pyclbr_example.py [21]
Super classes: ['Base']

Class: Mixin
File: pyclbr_example.py [25]
Method: method2 [29]

Class: MixinUser
File: pyclbr_example.py [32]
Super classes: ['Sub2', 'Mixin']
Method: method1 [36]
Method: method2 [39]
Method: method3 [42]

Scanning for Functions

The other public function in pyclbr is readmodule_ex(). It does everything that readmodule() does, and adds functions to the result set.

import pyclbr
import os
from operator import itemgetter

example_data = pyclbr.readmodule_ex('pyclbr_example')

for name, data in sorted(example_data.items(), key=lambda x:x[1].lineno):
if isinstance(data, pyclbr.Function):
print 'Function: {0} [{1}]'.format(name, data.lineno)

Each Function object has properties much like the Class object.

$ python pyclbr_readmodule_ex.py
Function: my_function [45]

See also

pyclbr
The standard library documentation for this module.
inspect
The inspect module can discover more meta-data about classes and functions, but requires importing the code.
tokenize
The tokenize module parses Python source code into tokens.

PyMOTW Home

The canonical version of this article