Although we are using some of Python’s object-oriented features, the programs from the last two chapters are not really object-oriented because they don’t represent the relationships between programmer-defined types and the functions that operate on them. The next step is to transform those functions into methods that make the relationships explicit.

Object-oriented features

  • Programs include class and method definitions.
  • Most of the computation is expressed in terms of operations on objects.
  • Objects often represent things in the real world, and methods often correspond to the ways things in the real world interact.

Recall in Time1.py, every function takes at least one Time object as an argument.

This observation is the motivation for methods; a method is a function that is associated with a particular class. We have seen methods for strings, lists, dictionaries and tuples. In this chapter, we will define methods for programmer-defined types.

Methods vs. functions

  • Methods are defined inside a class definition in order to make the relationship between the class and the method explicit.
  • The syntax for invoking a method is different from the syntax for calling a function.

Printing ojects

class Time:
    """Represents the time of day."""

def print_time(time):
    print(f'{time.hour:02}:{time.minute:02}:{time.second:02}')

start = Time()
start.hour = 9
start.minute = 45
start.second = 0
print_time(start)

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:
    def print_time(self):
        print(f'{self.hour:02}:{self.minute:02}:{self.second:02}')

start = Time()
start.hour = 9
start.minute = 45
start.second = 0

# Two ways to call:
Time.print_time(start)
start.print_time()
  • 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.”

Exercise 01

Rewrite time_to_int as a method.

A more complicated example

    def is_after(self, other):
        return self.time_to_int() > other.time_to_int()
    
# To use this method, you have to invoke it on one object 
# and pass the other as an argument:

end = start.increment(1337)
end.is_after(start)    

One nice thing about this syntax is that it almost reads like English: “end is after start?”

The __init__ method

The init method (short for “initialization”) is a special method that gets invoked when an object is instantiated. Its full name is __init__ (two underscore characters, followed by init, and then two more underscores). An init method for the Time class might look like this:

    def __init__(self, hour=0, minute=0, second=0): 
        self.hour = hour
        self.minute = minute
        self.second = second
time = Time()
time.print_time()
time = Time (9)
time.print_time()
time = Time(9, 45)
time.print_time()

Exercise 02

Write an __init__ method for the Point class that takes x and y as optional parameters and assigns them to the corresponding attributes.

The __str__ method

__str__ is a special method that is supposed to return a string representation of an object.

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

    def __str__(self):
        return f'{self.hour:02}:{self.minute:02}:{self.second:02}'

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

time = Time(9, 45)
print(time)

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 03

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

Operator overloading

    def __add__(self, other):
        seconds = self.time_to_int() + other.time_to_int()
        return int_to_time(seconds)
start = Time(9, 45)
duration = Time(1, 35)
print(start + duration)

When you apply the + operator to Time objects, Python invokes __add__. When you print the result, Python invokes __str__. So there is a lot happening behind the scenes!

Changing the behavior of an operator so that it works with programmer-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/3/reference/datamodel.html#specialnames.

Exercise 04

Write an __add__ method for the Point class.

Exercise 05 (group work)

Write a definition for a class of anything you want. You have to use the following methods:

  1. __init__ method that initializes some attributes. One of the attributes has to be an empty list.

  2. __str__ method that returns a string that reasonably represent the thing.

  3. A special method that overloads the one type of operators.

  4. Some other methods that reasonably represent the thing's actions, inclduing one method that takes an object of any type and adds it to the attribute of type list.

Test your code by creating two objects and using all the methods.