← 学习库 Think Python 2e 目录

Chapter 18

> 来源: Think Python 2e (Allen B. Downey)

> 原页: https://greenteapress.com/thinkpython/html/thinkpython018.html

\

[插图缺失:thinkpython017.html]
[ [](thinkpython019.html)

------------------------------------------------------------------------

Chapter 17   Classes and methods

Code examples from this chapter are available from http://thinkpython.com/code/Time2.py.

17.1   Object-oriented features

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:

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 Time0 and Time00008 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:

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.

17.2   Printing objects

In Chapter 16, we defined a class named Time and in Exercise 1, you wrote a function named Time00012:

Time00013

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

Time00015

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

Time00017

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

Time00019

In this use of dot notation, Time is the name of the class, and Time00021 is the name of the method. Time0 is passed as a parameter.

The second (and more concise) way is to use method syntax:

Time00023

In this use of dot notation, Time00024 is the name of the method (again), and Time0 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 Time0 is assigned to Time.

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

Time00030

The reason for this convention is an implicit metaphor:

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 Time00035 (from Section 16.4) as a method. It is probably not appropriate to rewrite Time00036 as a method; what object you would invoke it on?

17.3   Another example

Here’s a version of Time00037 (from Section 16.3) rewritten as a method:

Time00038

This version assumes that Time00039 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 Time00040:

Time00041

The subject, Time0, gets assigned to the first parameter, Time. The argument, Time, gets assigned to the second parameter, Time000.

This mechanism can be confusing, especially if you make an error. For example, if you invoke Time00046 with two arguments, you get:

Time00047

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.

17.4   A more complicated example

Time0004 (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 Time and the second parameter Time0:

Time00051

To use this method, you have to invoke it on one object and pass the other as an argument:

Time00052

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

17.5   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 Time0005 (two underscore characters, followed by Time, and then two more underscores). An init method for the Time class might look like this:

Time00056

It is common for the parameters of Time0005 to have the same names as the attributes. The statement

Time00058

stores the value of the parameter Time as an attribute of Time.

The parameters are optional, so if you call Time with no arguments, you get the default values.

Time00062

If you provide one argument, it overrides Time:

Time00064

If you provide two arguments, they override Time and Time00.

Time00067

And if you provide three arguments, they override all three default values.

Exercise 2  

Write an init method for the Time0 class that takes x and x as optional parameters and assigns them to the corresponding attributes.

17.6   The Time000 method

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

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

Time00075

When you Time0 an object, Python invokes the x00 method:

Time00078

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

Exercise 3  

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

17.7   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 Time000 for the Time class, you can use the x operator on Time objects.

Here is what the definition might look like:

Time00086

And here is how you could use it:

Time00087

When you apply the x operator to Time objects, Python invokes Time000. When you print the result, Python invokes Time000. 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 Time000. For more details, see Time00092.

Exercise 4  

Write an x00 method for the Point class.

17.8   Type-based dispatch

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 Time000 that checks the type of Time0 and invokes either Time0009 or Time00097:

Time00098

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

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

Here are examples that use the x operator with different types:

Time00106

Unfortunately, this implementation of addition is not commutative. If the integer is the first operand, you get

Time00107

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 Time0010, which stands for “right-side add.” This method is invoked when a Time object appears on the right side of the x operator. Here’s the definition:

Time00110

And here’s how it’s used:

Time00111

Exercise 5  

Write an x00 method for Points that works with either a Point object or a tuple:

17.9   Polymorphism

Type-based dispatch is useful when it is necessary, but (fortunately) it is not always necessary. Often you can avoid it by writing functions that work correctly for arguments with different types.

Many of the functions we wrote for strings will actually work for any kind of sequence. For example, in Section 11.1 we used Time00113 to count the number of times each letter appears in a word.

Time00114

This function also works for lists, tuples, and even dictionaries, as long as the elements of x are hashable, so they can be used as keys in x.

Time00117

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

Since Time objects provide an x00 method, they work with x00:

Time00121

In general, if all of the operations inside a function work with a given type, then the function works with that type.

The best kind of polymorphism is the unintentional kind, where you discover that a function you already wrote can be applied to a type you never planned for.

17.10   Debugging

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 Time001 (see Section 15.7).

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

Time00124

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

Time00125

Time00126 traverses the items in the object’s dictionary and prints each attribute name and its corresponding value.

The built-in function Time001 takes an object and an attribute name (as a string) and returns the attribute’s value.

17.11   Interface and implementation

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 Time00128, Time0012, and Time0013.

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 Time, Time00, and Time00.

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 Time0013, 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 Time00136.

Exercise 6  

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

17.12   Glossary

object-oriented language:

A language that provides features, such as user-defined classes and method syntax, that facilitate object-oriented programming.

object-oriented programming:

A style of programming in which data and the operations that manipulate it are organized into classes and methods.

method:

A function that is defined inside a class definition and is invoked on instances of that class.

subject:

The object a method is invoked on.

operator overloading:

Changing the behavior of an operator like x so it works with a user-defined type.

type-based dispatch:

A programming pattern that checks the type of an operand and invokes different functions for different types.

polymorphic:

Pertaining to a function that can work with more than one type.

information hiding:

The principle that the interface provided by an object should not depend on its implementation, in particular the representation of its attributes.

17.13   Exercises

Exercise 7  

This exercise is a cautionary tale about one of the most common, and difficult to find, errors in Python. Write a definition for a class named Time0014 with the following methods:

  1. An Time0014 method that initializes an attribute named Time00145 to an empty list.
  2. A method named Time00146 that takes an object of any type and adds it to Time00147.
  3. A Time001 method that returns a string representation of the Kangaroo object and the contents of the pouch.

Test your code by creating two Time0014 objects, assigning them to variables named Time0 and x00, and then adding x00 to the contents of Time0’s pouch.

Download Time00154. It contains a solution to the previous problem with one big, nasty bug. Find and fix the bug.

If you get stuck, you can download Time00155, which explains the problem and demonstrates a solution.

Exercise 8  

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 Time00156.

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.

Time00157

Time0 is an RGB tuple; that is, the elements are Red-Green-Blue levels between 0.0 and 1.0 (see Time00159).

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.

The following loop creates a cube of spheres:

Time00160

  1. Put this code in a script and make sure it works for you.
  2. 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.
  3. Download Time00161 and use the function Time00162 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.

You can see my solution at Time00163.

Contribute

If you would like to make a contribution to support my books, you can use the button below. Thank you!

Pay what you want:

Small $1.00 USD Medium $5.00 USD Large $10.00 USD X-Large $20.00 USD XX-Large $50.00 USD

Are you using one of our books in a class?

We'd like to know about it. Please consider filling out this short survey.


------------------------------------------------------------------------

\

[插图缺失:thinkpython017.html]
[ [](thinkpython019.html)

---

← Chapter 17Chapter 19 →