Next / Previous / Contents / TCC Help System / NM Tech homepage

11.9. Special methods: Sorting snail race data

Certain method names have special meaning to Python; each of these special method names starts with two underbars, “__”.

A class's constructor method, __init__(), is an example of a special method. Whenever you use the class's name as if it were a function, in an expression like “SnailRun('Judy', 67.3)”, Python executes the constructor method to build the new instance.

There is a full list of all the Python special method names in the Python quick reference. Next we will look at another special method, __cmp__, that Python calls whenever you compare two instances of that class.

Going back to our snail-racing application, an instance of the SnailRun class contains everything we need to know about one snail's performance: its name in the .name attribute and its finish time in the .time attribute.

However, using the tuple representation back in Section 11.4, “Snail-oriented data structures: A list of tuples”, we were able to put a collection of these tuples into a list, and sort the list so that they were ordered by finish time, with the winner first. Let's see what we need to add to class SnailRun so that we can sort a list of them into finish order by calling the .sort() method on the list.

First, a bit of review. Back in Section 6.1, “Conditions and the bool type”, we learned about the built-in Python function cmp(x, y), which returns:

In a Python class, if you define a method named “__cmp__”, that method is called whenever Python compares two instances of the class. It must return a result using the same conventions as the built-in cmp() function: negative for “<”, zero for “==”, positive for “>”.

In the case of “class SnailRun”, we want the snail with the better finishing time to be considered less than the slower snail. So here is one way to define the __cmp__ method for our class:

    def __cmp__ ( self, other ):
        """Define how to compare two SnailRun instances.
        """
        if self.time < other.time:
            return -1
        elif self.time > other.time:
            return 1
        else:
            return 0

When this method is called, self is an instance of class SnailRun, and other should also be an instance of SnailRun.

However, this logic exactly duplicates what the built-in cmp() function does to compare two float values, so we can simplify it like this:

    def __cmp__ ( self, other ):
        """Define how to compare two SnailRun instances.
        """
        return cmp(self.time, other.time)

Let's look at another special method, __str__(). This one defines how Python converts an instance of a class into a string. It is called, for example, when you name an instance in a print statement, or when you pass an instance to Python's built-in str() function.

The __str__() method of a class returns a string value. It is up to the writer of the class what string value gets returned. As usual for Python methods, the self argument contains the instance. In the case of class SnailRun, we'll want to display the snail's name (.name attribute) and finishing time (.time attribute). Here's one possible version:

    def __str__ ( self ):
        """Return a string representation of self.
        """
        return "{0:8.1f} {1}".format(self.time, self.name)

This method will format the finishing time into an 8-character string, with one digit after the decimal point, followed by one space, then the snail's name.

Let's assume that the __cmp__() and __str__() methods have been added to our snails.py module, and show their use in some conversational examples.

>>> from snails import *
>>> sally4 = SnailRun('Sally', 88.8)
>>> jim4=SnailRun('Jim', 76.5)
>>> 

Now that we have two SnailRun instances, we can show how the __str__() method formats them for printing:

>>> print sally4
    88.8 Sally
>>> print jim4
    76.5 Jim
>>> 

We can also show the various ways that Python compares two instances using our new __cmp__() method.

>>> cmp(sally4,jim4)
1
>>> sally4 > jim4
True
>>> sally4 <= jim4
False
>>> sally4 < jim4
False
>>> 

Now that we have defined how instances are to be ordered, we can sort a list of them in order by finish time. First we throw them into the list in any old order:

>>> judy4 = SnailRun ( 'Judy', 67.3 )
>>> blake4 = SnailRun ( 'Blake', 181.4 )
>>> race4 = [sally4, jim4, judy4, blake4]
>>> for run in race4:
...     print run
... 
    88.8 Sally
    76.5 Jim
    67.3 Judy
   181.4 Blake
>>> 

The .sort() method on a list uses Python's cmp() function to compare items when sorting them, and this in turn will call our class's __cmp__() method to sort them by finishing time.

>>> race4.sort()
>>> for run in race4:
...     print run
... 
    67.3 Judy
    76.5 Jim
    88.8 Sally
   181.4 Blake
>>> 

For an extended example of a class that implements a number of special methods, see rational.py: An example Python class. This example shows how to define a new kind of numbers, and specify how operators such as “+” and “*” operate on instances.