Python is an object-oriented programming language, which means
that it provides features that support object-oriented
programming.

It is not easy to define object-oriented programming, but we have
already seen some of its characteristics:

Programs are made up of object definitions and function
definitions, and most of the computation is expressed in terms
of operations on objects.

Each object definition corresponds to some object or concept
in the real world, and the functions that operate on that object
correspond to the ways real-world objects interact.

For example, the Time class defined in Chapter 16
corresponds to the way people record the time of day, and the
functions we defined correspond to the kinds of things people do with
times. Similarly, the Point and Rectangle classes
correspond to the mathematical concepts of a point and a rectangle.

So far, we have not taken advantage of the features Python provides to
support object-oriented programming. These
features are not strictly necessary; most of them provide
alternative syntax for things we have already done. But in many cases,
the alternative is more concise and more accurately conveys the
structure of the program.

For example, in the Time program, there is no obvious
connection between the class definition and the function definitions
that follow. With some examination, it is apparent that 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 user-defined types.

Methods are semantically the same as functions, but there are
two syntactic differences:

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.

In the next few sections, we will take the functions from the previous
two chapters and transform them into methods. This transformation is
purely mechanical; you can do it simply by following a sequence of
steps. If you are comfortable converting from one form to another,
you will be able to choose the best form for whatever you are doing.

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:

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 1

Rewrite time_to_int (from Section 16.4) as a method.
It is probably not appropriate to rewrite int_to_time as a
method; what object you would invoke it on?

is_after (from Exercise 2) is slightly more complicated
because it takes two Time objects as parameters. In this case it is
conventional to name the first parameter self and the second
parameter other:

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:

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.

In the previous section we added two Time objects, but you
also might want to add an integer to a Time object. The
following is a version of __add__
that checks the type of other and invokes either
add_time or increment:

The built-in function isinstance takes a value and a
class object, and returns True if the value is an instance
of the class.

If other is a Time object, __add__ invokes
add_time. Otherwise it assumes that the parameter
is a number and invokes increment. This operation is
called a type-based dispatch because it dispatches the
computation to different methods based on the type of the
arguments.

The problem is, instead of asking the Time object to add an integer,
Python is asking an integer to add a Time object, and it doesn’t know
how to do that. But there is a clever solution for this problem: the
special method __radd__, which stands for “right-side add.”
This method is invoked when a Time object appears on the right side of
the + operator. Here’s the definition:

Functions that can work with several types are called polymorphic.
Polymorphism can facilitate code reuse. For example, the built-in
function sum, which adds the elements of a sequence, works
as long as the elements of the sequence support addition.

It is legal to add attributes to objects at any point in the execution
of a program, but if you are a stickler for type theory, it is a
dubious practice to have objects of the same type with different
attribute sets. It is usually a good idea to
initialize all of an object’s attributes in the init method.

If you are not sure whether an object has a particular attribute, you
can use the built-in function hasattr (see Section 15.7).

Another way to access the attributes of an object is through the
special attribute __dict__, which is a dictionary that maps
attribute names (as strings) and values:

>>> p = Point(3, 4)
>>> print p.__dict__
{'y': 4, 'x': 3}

For purposes of debugging, you might find it useful to keep this
function handy:

One of the goals of object-oriented design is to make software more
maintainable, which means that you can keep the program working when
other parts of the system change, and modify the program to meet new
requirements.

A design principle that helps achieve that goal is to keep
interfaces separate from implementations. For objects, that means
that the methods a class provides should not depend on how the
attributes are represented.

For example, in this chapter we developed a class that represents
a time of day. Methods provided by this class include
time_to_int, is_after, and add_time.

We could implement those methods in several ways. The details of the
implementation depend on how we represent time. In this chapter, the
attributes of a Time object are hour, minute, and
second.

As an alternative, we could replace these attributes with
a single integer representing the number of seconds
since midnight. This implementation would make some methods,
like is_after, easier to write, but it makes some methods
harder.

After you deploy a new class, you might discover a better
implementation. If other parts of the program are using your
class, it might be time-consuming and error-prone to change the
interface.

But if you designed the interface carefully, you can
change the implementation without changing the interface, which
means that other parts of the program don’t have to change.

Keeping the interface separate from the implementation means that
you have to hide the attributes. Code in other parts of the program
(outside the class definition) should use methods to read
and modify the state of the object. They should not access the
attributes directly. This principle is called information hiding;
see http://en.wikipedia.org/wiki/Information_hiding.

Exercise 6

Download the code from this chapter
(http://thinkpython.com/code/Time2.py). Change the attributes
of Time to be a single integer representing seconds since
midnight. Then modify the methods (and the function
int_to_time) to work with the new implementation. You should
not have to modify the test code in main. When you are done,
the output should be the same as before. Solution:
http://thinkpython.com/code/Time2_soln.py

Visual is a Python module that provides 3-D graphics. It is
not always included in a Python installation, so you might have
to install it from your software repository or, if it’s not there,
from http://vpython.org.

The following example creates a 3-D space that is 256 units
wide, long and high, and sets the “center” to be the
point (128,128,128). Then it draws a blue sphere.

If you run this code, you should see a window with a black
background and a blue sphere. If you drag the middle button
up and down, you can zoom in and out. You can also rotate
the scene by dragging the right button, but with only one
sphere in the world, it is hard to tell the difference.

Modify the program so that each sphere in the cube
has the color that corresponds to its position in RGB space.
Notice that the coordinates are in the range 0–255, but
the RGB tuples are in the range 0.0–1.0.

Download http://thinkpython.com/code/color_list.py
and use the function read_colors to generate a list
of the available colors on your system, their names and
RGB values. For each named color draw a sphere in the
position that corresponds to its RGB values.