# Chapter 17 Classes and methods 類和方法
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.
> 前兩章我們已經用到了Python 的一些面向對象的特性了,但那寫程序實際上并不算是真正面向對象的,因為它們并沒能夠表現出用戶自定義類型與對這些類型進行運算的函數之間的關系。所以接下來的移步就是要把這些函數轉換成方法,讓這些關系更明確。
Code examples from this chapter are available from [Here](http://thinkpython2.com/code/Time2.py), and solutions to the exercises are in [Here](http://thinkpython2.com/code/Point2_soln.py).
> 本章的樣例代碼可以在 [這里下載](http://thinkpython2.com/code/Time2.py),然后練習題的樣例代碼可以在[這里下載](http://thinkpython2.com/code/Point2_soln.py)。
## 17.1 Object-oriented features 面向對象的特性
Python is an object-oriented programming language, which means that it provides features that support object-oriented programming, which has these defining characteristics:
> Python 是一種面向對象的編程語言,這就意味著它提供了一些支持面向對象編程的功能,有以下這些特點:
? 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.
> 對象往往代表著現實世界中的事物,方法則相對應地代表著現實世界中事物之間的相互作用。
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 in Chapter 15 correspond to the mathematical concepts of a point and a rectangle.
> 例如,第16章中定義的 Time 類就代表了人們生活中計算一天時間的方法,然后當時咱們寫的那些函數就對應著人們對時間的處理。同理,在第15章定義的 Point 和 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.
> 到此為止,我們還沒有用到 Python 提供的用于面向對象編程的高級功能。這些高級功能并不是嚴格意義上必須使用的;它們大多是提供了一些我們已經實現的功能的一種備選的語法形式。不過在很多情況下,這種備選的模式更加簡潔,也能更加準確地表述程序的結構。
For example, in Time1.py 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.
> 例如,在 Time1.py 里面,類的定義和后面的函數定義就沒有啥明顯的練習。測試一下就會發現,每一個后續的函數里面都至少用了一個 Time 對象作為參數。
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 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 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 Section 16.1, you wrote a function named print_time:
> 在16.1,我們定義過一個名為Time 的類,當時寫過月名為 print_time 的函數:
```Python
class Time:
"""Represents the time of day."""
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
```
To call this function, you have to pass a Time object as an argument:
> 要調用這個函數,就必須給傳遞過去一個TIme 對象作為參數:
```Python
>>> start = Time()
>>> start = Time()
>>> start.hour = 9
>>> start.hour = 9
>>> start.minute = 45
>>> start.minute = 45
>>> start.second = 00
>>> start.second = 00
>>> print_time(start)
>>> 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.
> 要讓 print_time 成為一個方法,只需要把函數定義內容放到類定義里面去。一定要注意縮進的變化哈。
```Python
class Time:
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
```
Now there are two ways to call print_time. The first (and less common) way is to use function syntax:
> 現在就有兩種方法來調用 print_time 這個函數了。第一種就是用函數的語法(一般大家不這么用):
```Python
>>> Time.print_time(start)
>>> 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.
> 上面這里用到了點號,Time 是類的名字,pritn_time 是方法的名字。start 就是傳過去的一個參數了。
The second (and more concise) way is to use method syntax:
> 另外一種形式就是用方法的語法(這個形式更簡潔很多):
```Python
>>> start.print_time()
>>> 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.
> 在上面這里也用了點號,print_time 依然還是方法名字,然后 start 是調用方法所在的對象,也叫做主語。這里就如同句子中的主語一樣,方法調用的主語就是方法的歸屬者。
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:
> 在方法內部,主語被用作第一個參數,所以在上面的例子中中,start 就被賦值給了 time。
> 按照慣例,方法的第一個參數也叫做 self,所以剛剛的 print_time 函數可以以如下這種更通用的形式來寫:
```Python
class Time:
def print_time(self):
print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.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.”
> 函數調用的語法里面,print_time(start),就表明了函數是主動的。這句語句的意思就相當于說,『嘿,print_time 函數!給你這個對象,你來打印輸出一下。』
? In object-oriented programming, the objects are the active agents. A method invocation like start.print_time() says “Hey start! Please print yourself.”
> 在面向對象的編程中,對象是主動的。方法的調用,比如 start.rint_time(),就像是說,『嘿,start,你打印輸出一下你自己』
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 (or methods), and makes it easier to maintain and reuse code.
> 看上去這樣改了之后客氣了不少,實際上不止如此,還有更多用處,只是不太明顯。目前我們看到過的例子里面,這樣改寫還沒有造成什么區別。但是有的時候,從函數轉為對象,能夠讓函數(或者方法)更加通用,也讓程序更容易維護,還便于代碼的重用。
As an exercise, rewrite time_to_int (from Section 16.4) as a method. You might be tempted to rewrite int_to_time as a method, too, but that doesn’t really make sense because there would be no object to invoke it on.
> 做個練習吧,重寫一下 time_to_int(參見16.4),把這個函數寫成一個方法。你也可以試著把 int_to_time 也攜程方法,不過這可能不太行得通,因為把這個函數改成方法的話,沒有對象來調用方法。
## 17.3 Another example 另外一個例子
Here’s a version of increment (from Section 16.3) rewritten as a method:
> 下面是 increment 函數(參見16.4)被改寫成的方法:
```Python
# 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. Also, note that it is a pure function, not a modifier.
Here’s how you would invoke increment:
> 這一版本的前提是 time_to_int 已經被改寫成方法了。另外也要注意到,這是一個純函數,而不是修改器。
> 下面是調用 increment 的示范:
```Python
>>> start.print_time()
>>> start.print_time()
09:45:00
>>> end = start.increment(1337)
>>> end = start.increment(1337)
>>> end.print_time()
>>> 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:
> 主語,start,用自己(self)賦值給第一個參數。然后參數,1337,賦值給了第二個參數,秒值seconds。
> 這種表述挺混亂,如果弄錯了就更麻煩了。比如,如果你用兩個參數調用了 increment 函數,你會得到如下的錯誤:
```Python
>>> end = start.increment(1337, 460)
>>> end = start.increment(1337, 460)
TypeError: increment() takes 2 positional arguments but 3 were 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.
> 這個錯誤信息剛開始看上去還挺不好理解,因為括號里面確實是只有兩個參數。不過實際上主語也會把自己當做一個參數,所以總共實際上是有三個參數了。
By the way, a positional argument is an argument that doesn’t have a parameter name; that is, it is not a keyword argument. In this function call:
> 另外,有一種參數叫位置參數,就是沒有參數的名字;這種參數就和關鍵字參數不同了。下面這個函數調用中:
```Bash
sketch(parrot, cage, dead=True)
```
parrot and cage are positional, and dead is a keyword argument.
> parrot 和 cage 都是位置參數,dead 是關鍵字參數。
## 17.4 A more complicated example 更復雜點的例子
Rewriting is_after (from Section 16.1) 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:
> 重寫 is_after(參見16.1),這就比較有難度了,因為這個函數接收兩個 Time 對象作為參數。在這個情況下,一般就把第一個參數命名為 self,第二個命名為 other:
```Python
# inside class Time:
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:
> 要使用這個方法,就必須在一個對象后面調用,然后用另外一個對象作為參數:
```Python
>>> end.is_after(start)
>>> end.is_after(start)
True
```
One nice thing about this syntax is that it almost reads like English: “end is after start?”
> 這里就體現出一種語法上的好處了,因為讀起來基本就根英語是一樣的嗯:『end is after start?』
## 17.5 The init method init方法
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:
> init 方法(就是對『initialization』的縮寫,初始化的意思,這個方法相當于C++中的構造函數)是一種特殊的方法,在對象被實例化的時候被調用。這個方法的全名是\_\_init\_\_(兩個下劃線,然后是 init,然后還是兩個下劃線)。在 Time 類當中,init 方法示例如下:
```Python
# inside class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second
```
It is common for the parameters of \_\_init\_\_ to have the same names as the attributes. The statement
> 一般情況下,\_\_init\_\_ 方法里面的參數與屬性變量的名字是相同的。下面這個語句
```Python
self.hour = hour
```
stores the value of the parameter hour as an attribute of self.
就存儲了參數 hour 的值,賦給了屬性變量hour本身。
The parameters are optional, so if you call Time with no arguments, you get the default values.
> 這些參數都是可選的,所以如果你調用 Time 但不給任何參數,得到的就是默認值。
```Python
>>> time = Time()
>>> time = Time()
>>> time.print_time()
>>> time.print_time()
00:00:00
```
If you provide one argument, it overrides hour:
> 如果你提供一個參數,就先覆蓋 hour 的值:
```Python
>>> time = Time (9)
>>> time = Time (9)
>>> time.print_time()
>>> time.print_time()
09:00:00
```
If you provide two arguments, they override hour and minute.
> 提供兩個參數,就先后覆蓋了 hour 和 minute 的值。
```Python
>>> time = Time(9, 45)
>>> time = Time(9, 45)
>>> time.print_time()
>>> time.print_time()
09:45:00
```
And if you provide three arguments, they override all three default values.
> 如果你給出三個參數,就依次覆蓋掉所有三個默認值了。
As an exercise, write an init method for the Point class that takes x and y as optional parameters and assigns them to the corresponding attributes.
> 做一個練習,寫一個 Point 類的 init 方法,接收 x 和 y 作為可選參數,然后賦值給對應的屬性。
## 17.6 The \_\_str\_\_ method str 方法
\_\_str\_\_ is a special method, like \_\_init\_\_, that is supposed to return a string representation of an object.
> \_\_str\_\_ 是一種特殊的方法,就跟\_\_init\_\_差不多,str 方法是接收一個對象,返回一個代表該對象的字符串。
For example, here is a str method for Time objects:
> 例如,下面就是Time 對象的一個 str 方法:
```Python
# inside class Time:
def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
```
When you print an object, Python invokes the str method:
> 這樣當你用 print 打印輸出一個對象的時候,Python 就會調用這個 str 方法:
```Python
>>> time = Time(9, 45)
>>> time = Time(9, 45)
>>> print(time) 09:45:00
>>> 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.
> 寫一個新的類的時候,總要先寫出來 \_\_init\_\_ 方法,這樣有利于簡化對象的初始化,還要寫個 \_\_str\_\_ 方法,這個方法在調試的時候很有用。
As an exercise, write a str method for the Point class. Create a Point object and print it.
> 做個練習,寫一下 Point 這個類的 str 方法。創建一個 Point 對象,然后用 print 輸出一下。
## 17.7 Operator overloading 運算符重載
By defining other special methods, you can specify the behavior of operators on programmer-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:
> 通過定義一些特定的方法,咱們就能針對自定義類型,讓運算符有特定的作用。比如,如果你在 Time 類中定義了一個名字為\_\_add\_\_的方法,你就可以對 Time 對象使用『+』加號運算符。
```Python
# 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:
> 使用方法如下所示:
```Python
>>> start = Time(9, 45)
>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> duration = Time(1, 35)
>>> print(start + duration)
>>> 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 a lot happening behind the scenes!
> 當你針對 Time 對象使用加號運算符的時候,Python 就會調用你剛剛自定義的 \_\_add\_\_ 方法。當你用 print 輸出結果的時候,Python 調用的是你自定義的 \_\_str\_\_ 方法。所以實際上面這個簡單的例子背后可不簡單。
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 [Here](http://docs.python.org/3/reference/datamodel.html#specialnames).
> 針對用戶自定義類型,讓運算符有相應的行為,這就叫做運算符重載。Python 當中每一個運算符都有一個對應的方法,比如\_\_add\_\_。更多內容可以看一下 [這里的文檔](http://docs.python.org/3/reference/datamodel.html#specialnames)。
As an exercise, write an add method for the Point class.
> 做個練習,給 Point 類寫一個加法的方法。
## 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 \_\_add\_\_ that checks the type of other and invokes either add_time or increment:
> 在前面的章節中,我們把兩個 Time 對象進行了相加,但也許有時候需要把一個整數加到 Time 對象上面。下面這一個版本的\_\_add\_\_方法就能夠實現檢查類型,然后調用add_time 方法或者是 increment 方法:
```Python
# inside class Time:
def __add__(self, other):
if isinstance(other, Time):
return self.add_time(other)
else:
return self.increment(other)
def add_time(self, other):
seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)
def increment(self, seconds):
seconds += self.time_to_int()
return int_to_time(seconds)
```
The built-in function isinstance takes a value and a class object, and returns True if the value is an instance of the class.
> 內置函數isinstance 接收一個值和一個類的對象,如果該值是這個類的一個實例,就會返回真。
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.
> 如果拿來相加的是一個 Time 對象,\_\_add\_\_就會調用 add_time 方法。其他情況下,程序會把參數當做一個數字,然后就調用 increment 方法。這種運算就是根據對象進行的,因為在針對不同類型參數的時候,運算符會進行不同的計算。
Here are examples that use the + operator with different types:
> 下面的例子中,就展示了用不同類型變量來相加的效果:
```Python
>>> start = Time(9, 45)
>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> duration = Time(1, 35)
>>> print(start + duration)
>>> print(start + duration)
11:20:00
>>> print(start + 1337)
>>> print(start + 1337)
10:07:17
```
Unfortunately, this implementation of addition is not commutative. If the integer is the first operand, you get
> 然而不幸的是,這個加法運算不滿足交換率。如果整數放到首位,就會得到如下所示的錯誤了:
```Python
>>> print(1337 + start)
>>> print(1337 + start)
TypeError: unsupported operand type(s) for +: 'int' and 'instance'
```
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. 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:
> 這里的問題就在于,Python 并沒有讓一個 Time 對象來加一個整數,而是去調用了整形的加法去把一個 Time 對象加到整數上面去,這就用系統原本的加法,而這個加法不能處理 Time 對象。有一個很聰明的方法來解決這個問題:用一個特殊的方法\_\_radd\_\_,這個方法的意思就是『右加』。在一個 Time 對象出現在加號運算符右側的時候,該方法就會被調用了。下面就是這個方法的定義:
```Python
# inside class Time:
def __radd__(self, other):
return self.__add__(other)
```
And here’s how it’s used:
> 使用如下所示:
```Python
>>> print(1337 + start)
>>> print(1337 + start)
10:07:17
```
As an exercise, write an add method for Points that works with either a Point object or a tuple:
> 做個練習,為 Point 類來寫一個加法的方法,要求能處理Point 對象或者一個元組:
? If the second operand is a Point, the method should return a new Point whose x coordinate is the sum of the x coordinates of the operands, and likewise for the y coordinates.
> 如果第二個運算數是一個Point,該方法就應該返回一個新的Point,新點的橫縱坐標分別為兩個點坐標相加。
? If the second operand is a tuple, the method should add the first element of the tuple to the x coordinate and the second element to the y coordinate, and return a new Point with the result.
> 如果第二個運算數是一個元組,該方法就要把元組中第一個元素加到橫坐標上,把第二個元素加到縱坐標上面,然后用計算出來的坐標返回一個新的點。
## 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 also work for other sequence types. For example, in Section 11.2 we used histogram to count the number of times each letter appears in a word.
> 我們之前為字符串寫的很多函數,也都可以用到其他序列類型上面。比如在11.2我們用 histogram 來統計一個單詞中每個字母出現的次數。
```Python
def histogram(s):
d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] = d[c]+1
return d
```
This function also works for lists, tuples, and even dictionaries, as long as the elements of s are hashable, so they can be used as keys in d.
> 這個函數也可以用于列表、元組,甚至字典,只要s 的元素是散列的,就能用做 d 當中的鍵。
```Python
>>> t = ['spam', 'egg', 'spam', 'spam', 'bacon', 'spam']
>>> t = ['spam', 'egg', 'spam', 'spam', 'bacon', 'spam']
>>> histogram(t)
>>> histogram(t)
{'bacon': 1, 'egg': 1, 'spam': 4}
```
Functions that 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.
> 針對不同類型都可以運行的函數,就是多態的了。多態能夠有利于代碼復用。比如內置的函數 sum,是用來把一個序列中所有的元素加起來,就可以適用于所有能夠相加的序列元素。
Since Time objects provide an add method, they work with sum:
> Time 對象有了自己的加法方法,就可以與 sum 函數來配合使用了:
```Python
>>> t1 = Time(7, 43)
>>> t1 = Time(7, 43)
>>> t2 = Time(7, 41)
>>> t2 = Time(7, 41)
>>> t3 = Time(7, 37)
>>> t3 = Time(7, 37)
>>> total = sum([t1, t2, t3])
>>> total = sum([t1, t2, t3])
>>> print(total)
>>> print(total)
23:01:00
```
In general, if all of the operations inside a function work with a given type, 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 have objects with the same type that don’t have the same attributes, it is easy to make mistakes. It is considered a good idea to initialize all of an object’s attributes in the init method.
> 在程序運行的任意時刻都可以給對象增加屬性,不過如果你有多個同類對象卻又不具有相同的屬性,就容易出錯了。所以最好在對象實例化的時候就全部用 init 方法初始化對象的全部屬性。
If you are not sure whether an object has a particular attribute, you can use the built-in function hasattr (see Section 15.7).
> 如果你不確定一個對象是否有某個特定的屬性,你可以用內置的 hasattr 函數來嘗試一下(參考15.7)。
Another way to access attributes is the built-in function vars, which takes an object and returns a dictionary that maps from attribute names (as strings) to their values:
> 另外一種讀取屬性的方法是用內置函數 vars,這個函數會接收一個對象,然后返回一個字典,字典中的鍵值對就是屬性名的字符串與對應的值。
```Python
>>> p = Point(3, 4)
>>> p = Point(3, 4)
>>> vars(p)
>>> vars(p)
{'y': 4, 'x': 3}
```
For purposes of debugging, you might find it useful to keep this function handy:
> 出于調試目的,你估計也會發現下面這個函數隨時用一下會帶來很多便利:
```Python
def print_attributes(obj):
for attr in vars(obj):
print(attr, getattr(obj, attr))
```
print_attributes traverses the dictionary and prints each attribute name and its corresponding value.
The built-in function getattr takes an object and an attribute name (as a string) and returns the attribute’s value.
> print_attributes 會遍歷整個字典,然后輸出每一個屬性的名字與對應的值。
> 內置函數 getattr 會接收一個對象和一個屬性名字(以字符串形式),然后返回該屬性的值。
## 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 time_to_int, is_after, and add_time.
> 比如,在本章我們建立了一個表示一天中時間的類。該類提供的方法包括 time_to_int, is_after, 和 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.
> 我們可以用幾種不同方式來實現這些方法。這些實現的細節依賴于我們如何去表示時間。在本章,一個 Time 對象的屬性為時分秒三個變量。
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 other methods harder.
> 還有一種替代方案,我們就可以把這些屬性替換為一個單個的整形變量,表示從午夜零點到當前時間的秒的數目。這種實現方法可以讓一些方法更簡單,比如 is_after,但也讓其他方法更難寫了。
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.
> 但如果你仔細地設計好接口,你在改變實現的時候就不用去折騰了,這就意味著你程序的其他部位都不需要改動了。
## 17.12 Glossary 術語列表
object-oriented language:
A language that provides features, such as programmer-defined types and methods, 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.
> 主語:調用方法的對象。
positional argument:
An argument that does not include a parameter name, so it is not a keyword argument.
> 位置參數:一種參數,沒有參數名字,不是關鍵字參數。
operator overloading:
Changing the behavior of an operator like + so it works with a programmer-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 1 練習1
Download the code from this chapter from [Here](http://thinkpython2.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://thinkpython2.com/code/Time2_soln.py)
> 從[這里](http://thinkpython2.com/code/Time2.py)下載本章的代碼。把 Time 中的屬性改變成一個單獨的整型變量,用來表示自從午夜至今的秒數。然后修改一下各個方法(以及 int_to_time 函數),讓所有功能都能在新的實現下正常工作。盡量就讓自己不用去更改main 當中的測試代碼。你改完之后,輸出應該與之前相同。[樣例代碼](http://thinkpython2.com/code/Time2_soln.py)
### Exercise 2 練習2
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 Kangaroo with the following methods:
> 這個練習是一個廣為流傳的寓言故事,其中包含了一個使用 Python的時候最常見但也是最難發現的錯誤。寫一個名為袋鼠的類的定義,要求有如下的方法:
1. An \_\_init\_\_ method that initializes an attribute named pouch_contents to an empty list.
> 一個\_\_init\_\_方法,用來初始化一個名為 puntch_contents(就是袋鼠的袋子中內容的意思) 的屬性,把該屬性初始化為一個空列表。
2. A method named put_in_pouch that takes an object of any type and adds it to pouch_contents.
> 一個名為put_in_pouch 的方法,接收任意類型的一個對象,把這個對象放進 pouch_contents 中。
3. A \_\_str\_\_ method that returns a string representation of the Kangaroo object and the contents of the pouch.
> 一個\_\_str\_\_方法,返回袋鼠對象的字符串表示,以及袋鼠袋子中的內容。
Test your code by creating two Kangaroo objects, assigning them to variables named kanga and roo, and then adding roo to the contents of kanga’s pouch.
> 通過建立兩個袋鼠對象來測試一下你的代碼,把它們倆分別命名為 kanga 和 roo,然后把roo 添加到 kanga 的袋子中。
Download [This](http://thinkpython2.com/code/BadKangaroo.py). It contains a solution to the previous problem with one big, nasty bug. Find and fix the bug.
> 下載[這個代碼](http://thinkpython2.com/code/BadKangaroo.py)。里面包含了上面這個練習的一個樣例代碼,但這個代碼有很大很悲催的 bug。找出這個 bug 然后改過來吧。
If you get stuck, you can download [This](http://thinkpython2.com/code/GoodKangaroo.py), which explains the problem and demonstrates a solution.
> 如果你搞不定了,可以下載[這個代碼](http://thinkpython2.com/code/GoodKangaroo.py),這個代碼中解釋了整個問題,并且提供了一個可行的解決方案。
- 介紹
- Think Python
- Chapter 0 Preface 前言
- Chapter 1 The way of the program 編程之路
- Chapter 2 Variables, expressions and statements 變量,表達式,語句
- Chapter 3 Functions 函數
- Chapter 4 Case study: interface design 案例學習:交互設計
- Chapter 5 Conditionals and recursion 條件循環
- Chapter 6 Fruitful functions 有返回值的函數
- Chapter 7 Iteration 迭代
- Chapter 8 Strings 字符串
- Chapter 9 Case study: word play 案例學習:單詞游戲
- Chapter 10 Lists 列表
- Chapter 11 Dictionaries 字典
- Chapter 12 Tuples 元組
- Chapter 13 Case study: data structure selection 案例學習:數據結構的選擇
- Chapter 14 Files 文件
- Chapter 15 Classes and objects 類和對象
- Chapter 16 Classes and functions 類和函數
- Chapter 17 Classes and methods 類和方法
- Chapter 18 Inheritance 繼承
- Chapter 19 The Goodies 額外補充