Associated Reading

Printing objects

We defined a class named Time and a function print_time:

class Time(object):
    """Represents the time of day."""

def print_time(time):
    print(str(time.hour) + ":" + str(time.minute) + ":" + str(time.second))

To call this function, you have to pass a Time object as an argument:

>>> start = Time()
>>> start.hour = 9
>>> start.minute = 45
>>> start.second = 00
>>> print_time(start)
09:45:00

To make print_time a method, all we have to do is move the function definition inside the class definition. Notice the change in indentation.

class Time(object):
    def print_time(time):
        print(str(time.hour) + ":" + str(time.minute) + ":" + str(time.second))

Now there are two ways to call print_time. The first (and less common) way is to use function syntax:

>>> Time.print_time(start)
09:45:00

In this use of dot notation, Time is the name of the class, and print_time is the name of the method. start is passed as a parameter. The second (and more concise) way is to use method syntax:

>>> start.print_time()
09:45:00

In this use of dot notation, print_time is the name of the method (again), and start is the object the method is invoked on, which is called the subject. Just as the subject of a sentence is what the sentence is about, the subject of a method invocation is what the method is about. Inside the method, the subject is assigned to the first parameter, so in this case start is assigned to time.

By convention, the first parameter of a method is called self, so it would be more common to write print_time like this:

class Time(object):
    def print_time(self):
        print(str(time.hour) + ":" + str(time.minute) + ":" + str(time.second))

The reason for this convention is an implicit metaphor: The syntax for a function call, print_time(start), suggests that the function is the active agent. It says something like, “Hey print_time! Here’s an object for you to print.”

In object-oriented programming, the objects are the active agents. A method invocation like start.print_time() says “Hey start! Please print yourself.” This change in perspective might be more polite, but it is not obvious that it is useful. In the examples we have seen so far, it may not be. But sometimes shifting responsibility from the functions onto the objects makes it possible to write more versatile functions, and makes it easier to maintain and reuse code.

Exercise

def time_to_int(time):
    minutes = time.hour * 60 + time.minute
    seconds = minutes * 60 + time.second
    return seconds

def int_to_time(seconds):
    time = Time()
    minutes, time.second = divmod(seconds, 60)
    time.hour, time.minute = divmod(minutes, 60)
    return time

Write time_to_int as a method. It is probably not appropriate to rewrite int_to_time as a method; what object you would invoke it on?

Another example

Here’s a version of increment written as a method:

# inside class Time:

    def increment(self, seconds):
        seconds += self.time_to_int()
        return int_to_time(seconds)

This version assumes that time_to_int is written as a method, as in Exercise 1. Also, note that it is a pure function, not a modifier. Here’s how you would invoke increment:

>>> start.print_time()
09:45:00
>>> end = start.increment(1337)
>>> end.print_time()
10:07:17

The subject, start, gets assigned to the first parameter, self. The argument, 1337, gets assigned to the second parameter, seconds. This mechanism can be confusing, especially if you make an error. For example, if you invoke increment with two arguments, you get:

>>> end = start.increment(1337, 460)
TypeError: increment() takes exactly 2 arguments (3 given)

The error message is initially confusing, because there are only two arguments in parentheses. But the subject is also considered an argument, so all together that’s three.

The str method

str is a special method, like init, that is supposed to return a string representation of an object.

For example, here is a str method for Time objects:

# inside class Time:

    def __str__(self):
        return         print(str(time.hour) + ":" + str(time.minute) + ":" + str(time.second))

When you print an object, Python invokes the str method:

>>> time = Time(9, 45)
>>> print(time)
09:45:00

When I write a new class, I almost always start by writing init, which makes it easier to instantiate objects, and str, which is useful for debugging.

Exercise

Write a str method for the Point class. Create a Point object and print it.

Operator overloading

By defining other special methods, you can specify the behavior of operators on user-defined types. For example, if you define a method named add for the Time class, you can use the + operator on Time objects.

Here is what the definition might look like:

# inside class Time:

    def __add__(self, other):
        seconds = self.time_to_int() + other.time_to_int()
        return int_to_time(seconds)

And here is how you could use it:

>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print start + duration
11:20:00

When you apply the + operator to Time objects, Python invokes add. When you print the result, Python invokes str. So there is quite a lot happening behind the scenes! Changing the behavior of an operator so that it works with user-defined types is called operator overloading. For every operator in Python there is a corresponding special method, like add. For more details, see http://docs.python.org/2/reference/datamodel.html#specialnames.

Exercise

Write an add method for the Point class.