# Chapter 18 Inheritance 繼承
The language feature most often associated with object-oriented programming is inheritance. Inheritance is the ability to define a new class that is a modified version of an existing class. In this chapter I demonstrate inheritance using classes that represent playing cards, decks of cards, and poker hands.
> 面向對象編程最常被人提到的語言功能就是繼承了。繼承就是基于一個已有的類進行修改來定義一個新的類。在本章我會用一些例子來演示繼承,這些例子會用到一些類來表示撲克牌,成副的紙牌和撲克牌型。
If you don’t play poker, you can read about it at [Here](http://en.wikipedia.org/wiki/Poker), but you don’t have to; I’ll tell you what you need to know for the exercises.
> 如果你沒玩過撲克,你可以讀一下[這里的介紹](http://en.wikipedia.org/wiki/Poker),不過也沒必要;因為我等會會把練習中涉及到的相關內容給你解釋明白的。
Code examples from this chapter are available from [Here](http://thinkpython2.com/code/Card.py).
> 本章的代碼樣例可以在[這里](http://thinkpython2.com/code/Card.py)下載。
## 18.1 Card objects 紙牌對象
There are fifty-two cards in a deck, each of which belongs to one of four suits and one of thirteen ranks. The suits are Spades, Hearts, Diamonds, and Clubs (in descending order in bridge). The ranks are Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, and King. Depending on the game that you are playing, an Ace may be higher than King or lower than 2.
> 牌桌上面一共有52張撲克牌,每一張都屬于四種花色之一,并且是十三張牌之一。花色為黑桃,紅心,方塊,梅花(在橋牌中按照降序排列)。排列順序為 A,2,3,4,5,6,7,8,9,10,J,Q,K。根據具體玩的游戲的不同,A 可以比 K 大,也可以比2還小。
If we want to define a new object to represent a playing card, it is obvious what the attributes should be: rank and suit. It is not as obvious what type the attributes should be. One possibility is to use strings containing words like 'Spade' for suits and 'Queen' for ranks. One problem with this implementation is that it would not be easy to compare cards to see which had a higher rank or suit.
> 如果咱們要定義一個新的對象來表示一張牌,很明顯就需要兩個屬性了:點數以及花色。但這兩個屬性應該是什么類型呢,就不那么明顯了。一種思路是用字符串,就比如用『黑桃』來表示花色,『Q』來表示點數。不過這個實現方法不怎么方便,不好去比較紙牌的點數大小以及花色。
An alternative is to use integers to encode the ranks and suits. In this context, “encode” means that we are going to define a mapping between numbers and suits, or between numbers and ranks. This kind of encoding is not meant to be a secret (that would be “encryption”).
> 另外一種思路是用整數來編碼,以表示點數和花色。在這里,『編碼』的意思就是我們要建立一個從數值到花色或者從數值到點數的映射。這種編碼并不是為了安全的考慮(那種情況下用的詞是『encryption(也是編碼的意思,專用于安全領域)』)。
For example, this table shows the suits and the corresponding integer codes:
> 例如,下面這個表格就表示了花色與整數編碼之間的映射關系:
```Python
Spades ? 3
Hearts ? 2
Diamonds ? 1
Clubs ? 0
```
This code makes it easy to compare cards; because higher suits map to higher numbers, we can compare suits by comparing their codes.
> 這樣的編碼就比較易于比較牌的大小;因為高花色對應著大數值,我們對比一下編碼大小就能比較花色順序。
The mapping for ranks is fairly obvious; each of the numerical ranks maps to the corresponding integer, and for face cards:
> 牌面大小的映射就很明顯了;每一張牌都對應著相應大小的整數,對于有人像的幾張映射如下所示:
```Python
Jack ? 11
Queen ? 12
King ? 13
```
I am using the ? symbol to make it clear that these mappings are not part of the Python program. They are part of the program design, but they don’t appear explicitly in the code.
> 我這里用箭頭符號 ? 來表示映射關系,但這個符號并不是 Python 所支持的。這些符號是程序設計的一部分,但最終并不以這種形式出現在代碼里。
The class definition for Card looks like this:
> 這樣實現的紙牌類的定義如下所示:
```Python
class Card:
"""Represents a standard playing card."""
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
```
As usual, the init method takes an optional parameter for each attribute. The default card is the 2 of Clubs.
To create a Card, you call Card with the suit and rank of the card you want.
> 一如既往,init 方法可以為每一個屬性接收一個可選參數來初始化。默認的牌面為梅花2.
> 要建立一張紙牌,可以用你想要的花色和牌值調用 Card。
```Python
queen_of_diamonds = Card(1, 12)
```
## 18.2 Class attributes 類的屬性
In order to print Card objects in a way that people can easily read, we need a mapping from the integer codes to the corresponding ranks and suits. A natural way to do that is with lists of strings. We assign these lists to class attributes:
> 想要以易于被人理解的方式來用 print 打印輸出紙牌對象,我們就得建立一個從整形編碼到對應的牌值和花色的映射。最自然的方法莫過于用字符串列表來實現。咱們可以先把這些列表賦值到類的屬性中去:
```Python
# inside class Card:
suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']
def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])
```
Variables like suit_names and rank_names, which are defined inside a class but outside of any method, are called class attributes because they are associated with the class object Card.
> suit_names 和 rank_names 這樣的變量,都是在類內定義,但不在任何方法之內,這就叫做類的屬性,因為它們屬于類 Card。
This term distinguishes them from variables like suit and rank, which are called instance attributes because they are associated with a particular instance.
> 這種形式就把類的屬性與變量 suit 和 rank 區分開來,后面這兩個變量叫做實例屬性,因為這兩個屬性取決于具體的實例。
Both kinds of attribute are accessed using dot notation. For example, in\_\_str\_\_, self is a Card object, and self.rank is its rank. Similarly, Card is a class object, and Card.rank_names is a list of strings associated with the class.
> 這些屬性都可以用點號來讀取。比如,在\_\_str\_\_方法中,self 是一個 Card 對象,而 self.rank 就是該對象的 rank 變量。同理,Card 是一個class 對象,而 Card.rank_names 就是屬于該類的一個字符串列表。
Every card has its own suit and rank, but there is only one copy of suit_names and rank_names.
> 沒一張牌都有自己的花色和牌值,但都只有唯一的一套 suit_names 和 rank_names。
Putting it all together, the expression Card.rank_names[self.rank] means “use the attribute rank from the object self as an index into the list rank_names from the class Card, and select the appropriate string.”
> 放到一起,這個表達式Card.rank_names[self.rank]的意思就是『用對象 self 的 rank 屬性作為一個索引,從類 Card 中的rank_names 列表中選擇該索引位置的字符串。』
The first element of rank_names is None because there is no card with rank zero. By including None as a place-keeper, we get a mapping with the nice property that the index 2 maps to the string '2', and so on. To avoid this tweak, we could have used a dictionary instead of a list.
> rank_names 的一個元素是None空,因為沒有牌值為0的紙牌。包含 None 在內作為一個替位符,整個映射就很簡明,索引2的位置對應著就是字符串「2」,其他牌值依此類推。要是覺得這樣太別扭,咱們還可以用字典來替代列表。
With the methods we have so far, we can create and print cards:
> 目前已經有了這些方法了,咱們就可以創建和打印輸出紙牌了:
```Python
>>> card1 = Card(2, 11)
>>> card1 = Card(2, 11)
>>> print(card1)
>>> print(card1)
Jack of Hearts
```
* * *

Figure 18.1: Object diagram.
* * *
Figure 18.1 is a diagram of the Card class object and one Card instance. Card is a class object; its type is type. card1 is an instance of Card, so its type is Card. To save space, I didn’t draw the contents of suit_names and rank_names.
> 圖18.1是一個 Card 類對象以及一個 Card 實例的圖解。Card 是一個類對象(就是類的一個實例);它的類型是type。card1是 Card 的一個實例,所以它的類型是 Card。為了節省空間,我沒有畫出 suit_names 和 rank_names 的內容。
## 18.3 Comparing cards 對比牌值
For built-in types, there are relational operators (<, >, ==, etc.) that compare values and determine when one is greater than, less than, or equal to another. For programmer-defined types, we can override the behavior of the built-in operators by providing a method named \_\_lt\_\_, which stands for “less than”.
> 對于內置類型,直接就可以用關系運算符(<, >, ==,等等)比較兩個值來判斷二者的大小以及是否相等。對與用戶自定義類型,咱們就要覆蓋掉內置運算符的行為,這就需要提供一個名為\_\_lt\_\_的方法,這個lt 就是『less than』的縮寫,意思是『小于』。
\_\_lt\_\_ takes two parameters, self and other, and True if self is strictly less than other.
The correct ordering for cards is not obvious. For example, which is better, the 3 of Clubs or the 2 of Diamonds? One has a higher rank, but the other has a higher suit. In order to compare cards, you have to decide whether rank or suit is more important.
> \_\_lt\_\_接收兩個參數,一個是self,一個是另外一個對象,如果 self 嚴格小于另外一個對象,就返回真。
> 紙牌的牌值大小排列并不是很簡單。比如,梅花3和方塊2哪個更大呢?一個的牌值更高,但另一個的花色更高。所以要進行比較的話,你就得確定牌值和花色哪個更重要。
The answer might depend on what game you are playing, but to keep things simple, we’ll make the arbitrary choice that suit is more important, so all of the Spades outrank all of the Diamonds, and so on.
> 實際上這種關系還得取決于你玩的紙牌游戲中的規則,不過為了簡單起見,咱們就做一個武斷的選擇,就讓花色更重要,所以所有的黑桃都大于方塊,依此類推了。
With that decided, we can write \_\_lt\_\_:
> 確定好規則了,就可以寫這個\_\_lt\_\_方法了:
```Python
# inside class Card:
def __lt__(self, other):
# check the suits
if self.suit < other.suit:
return True
if self.suit > other.suit:
return False
# suits are the same... check ranks
return self.rank < other.rank
```
You can write this more concisely using tuple comparison:
> 用元組對比就可以把代碼寫得更簡潔了:
```Python
# inside class Card:
def __lt__(self, other):
t1 = self.suit, self.rank
t2 = other.suit, other.rank
return t1 < t2
```
As an exercise, write an \_\_lt\_\_ method for Time objects. You can use tuple comparison, but you also might consider comparing integers.
> 做個練習,為 Time 對象寫一個\_\_lt\_\_方法。可以用元組對比,不過也可以對比整數。
## 18.4 Decks 成副的紙牌
Now that we have Cards, the next step is to define Decks. Since a deck is made up of cards, it is natural for each Deck to contain a list of cards as an attribute.
> 現在咱們已經有了紙牌的類了,接下來的一不就是定義成副紙牌了。因為一副紙牌上是有各種牌,所以很自然就應該包含一個紙牌列表作為一個屬性了。
The following is a class definition for Deck. The init method creates the attribute cards and generates the standard set of fifty-two cards:
> 下面就是一個一副紙牌類的定義。init 方法建立了一個屬性 cards,然后生成了標準的五十二張牌來初始化。
```Python
class Deck:
def __init__(self):
self.cards = []
for suit in range(4):
for rank in range(1, 14):
card = Card(suit, rank)
self.cards.append(card)
```
The easiest way to populate the deck is with a nested loop. The outer loop enumerates the suits from 0 to 3. The inner loop enumerates the ranks from 1 to 13. Each iteration creates a new Card with the current suit and rank, and appends it to self.cards.
> 實現一副牌的最簡單方法就是用網狀循環了。外層循環枚舉花色從0到3一共四種。內層的循環枚舉從1到13的所有牌值。每一次循環都以當前的花色和牌值創建一個新的Card 對象,添加到 self.cards 列表中。
## 18.5 Printing the deck 輸出整副紙牌
Here is a \_\_str\_\_ method for Deck:
> 下面是 Deck 類的\_\_str\_\_方法:
```Python
#inside class Deck:
def __str__(self):
res = []
for card in self.cards:
res.append(str(card))
return '\n'.join(res)
```
This method demonstrates an efficient way to accumulate a large string: building a list of strings and then using the string method join. The built-in function str invokes the \_\_str\_\_ method on each card and returns the string representation.
> 上面的方法展示了累積大字符串的一種有效方法:建立一個字符串列表,然后用字符串方法 join 實現。內置函數 str 調用每一張牌的\_\_str\_\_方法,然后返回該張紙牌的字符串表示。
Since we invoke join on a newline character, the cards are separated by newlines. Here’s what the result looks like:
> 由于我們調用 join的位置在換行符后面,這樣這些紙牌就被換行符分開了。程序運行結果如下所示:
```Python
>>> deck = Deck()
>>> deck = Deck()
>>> print(deck)
>>> print(deck)
Ace of Clubs
2 of Clubs
3 of Clubs
...
10 of Spades
Jack of Spades
Queen of Spades
King of Spades
```
Even though the result appears on 52 lines, it is one long string that contains newlines.
> 雖然結果看上去是52行,但實際上只是一個包含了很多換行符的一個長字符串。
## 18.6 Add, remove, shuffle and sort 添加,刪除,洗牌和排序
To deal cards, we would like a method that removes a card from the deck and returns it. The list method pop provides a convenient way to do that:
> 要處理紙牌,我們還需要一個方法來從牌堆中拿出和放入紙牌。列表的 pop 方法很適合來完成這件任務:
```Python
#inside class Deck:
def pop_card(self):
return self.cards.pop()
```
Since pop removes the last card in the list, we are dealing from the bottom of the deck.
To add a card, we can use the list method append:
> pop 方法從列表中拿走最后一張牌,這樣就是從一副牌的末尾來處理。
> 要添加一張牌,可以用列表的 append 方法:
```Python
#inside class Deck:
def add_card(self, card):
self.cards.append(card)
```
A method like this that uses another method without doing much work is sometimes called a veneer. The metaphor comes from woodworking, where a veneer is a thin layer of good quality wood glued to the surface of a cheaper piece of wood to improve the appearance.
> 上面這種方法都是調用了其他的方法,而沒有做什么別的事情,所以也被叫做鑲板。這個比喻來自于木匠行業,鑲板就是一薄層的高端木料用膠水貼到廉價木料上面,來提高視覺效果。
In this case add_card is a “thin” method that expresses a list operation in terms appropriate for decks. It improves the appearance, or interface, of the implementation.
> 在剛剛的例子中,add_card 就相當于那個『高端』的方法,表示的是適用于處理紙牌的列表操作。這樣就提高了程序實現的可讀性,或者說改善了接口。
As another example, we can write a Deck method named shuffle using the function shuffle from the random module:
> 再舉一個例子,咱們再來給 Deck寫一個洗牌的方法,用 random(隨機的意思)模塊的 shuffle 方法:
```Python
# inside class Deck:
def shuffle(self):
random.shuffle(self.cards)
```
Don’t forget to import random.
As an exercise, write a Deck method named sort that uses the list method sort to sort the cards in a Deck. sort uses the \_\_lt\_\_ method we defined to determine the order.
> 一定別忘了導入 random 模塊。
> 做個練習吧,寫一個名為 sort 的方法給 Deck,使用列表的sort 方法來給 Deck 中的牌進行排序。sort 方法要用到我們之前寫過的 \_\_lt\_\_ 方法來確定順序。
## 18.7 Inheritance 繼承
Inheritance is the ability to define a new class that is a modified version of an existing class. As an example, let’s say we want a class to represent a “hand”, that is, the cards held by one player. A hand is similar to a deck: both are made up of a collection of cards, and both require operations like adding and removing cards.
> 繼承就是基于已有的類進行修改來獲取新類的能力。舉個例子,比方說我們需要一個表示『一手牌』的類,這個就是指一個牌手手中拿著的牌。『一手牌』和『一副牌』有些相似:都是由一系列的紙牌組成的,也都要有添加和移除紙牌的運算。
A hand is also different from a deck; there are operations we want for hands that don’t make sense for a deck. For example, in poker we might compare two hands to see which one wins. In bridge, we might compute a score for a hand in order to make a bid.
> 『一手牌』還和『一副牌』有所區別;對于手中的牌有一些運算并不適用于整副的牌。比如說,在撲克游戲中,我們可能需要對比兩手牌來看看哪一副勝利。在橋牌里面,還可能需要對手中的牌進行計分以決勝負。
This relationship between classes—similar, but different—lends itself to inheritance. To define a new class that inherits from an existing class, you put the name of the existing class in parentheses:
> 類之間這種相似又有區別的關系,就適合用繼承來實現了。要繼承一個已有的類來定義新類,就要把已有類的名字放到括號中,如下所示:
```Python
class Hand(Deck):
"""Represents a hand of playing cards."""
```
This definition indicates that Hand inherits from Deck; that means we can use methods like pop_card and add_card for Hands as well as Decks.
> 上面這樣的定義就表示了 Hand 繼承了 Deck;也就意味著我們可以在 Hands 中使用 Decks 中的那些方法,比如 pop_card 以及 add_card 等等。
When a new class inherits from an existing one, the existing one is called the parent and the new class is called the child.
> 當一個新類繼承了一個已有的類時,這個已有的類就叫做基類,新定義的類叫做子類。
In this example, Hand inherits \_\_init\_\_ from Deck, but it doesn’t really do what we want: instead of populating the hand with 52 new cards, the init method for Hands should initialize cards with an empty list.
If we provide an init method in the Hand class, it overrides the one in the Deckclass:
> 在本章的這個例子中,Hand 類從 Deck 類繼承了\_\_init\_\_方法,但這個方法和我們的需求還不一樣:Hand類的 init 方法應該用一個空列表來初始化手中的牌,而不是像 Deck 類中那樣用一整副52張牌。
```Python
# inside class Hand:
def __init__(self, label=''):
self.cards = []
self.label = label
```
When you create a Hand, Python invokes this init method, not the one in Deck.
> 像上面這樣改寫一下之后,這樣再建立一個 Hand 類的時候,Python 就會調用這個自定義的 init 方法,而不是 Deck 當中的。
```Python
>>> hand = Hand('new hand')
>>> hand = Hand('new hand')
>>> hand.cards []
>>> hand.cards []
>>> hand.label
>>> hand.label
'new hand'
```
The other methods are inherited from Deck, so we can use pop_card and add_card to deal a card:
> 其他方法都從 Deck 類中繼承了過來,所以我們就可以直接用 pop_card 和 add_card 方法來處理紙牌了:
```Python
>>> deck = Deck()
>>> deck = Deck()
>>> card = deck.pop_card()
>>> card = deck.pop_card()
>>> hand.add_card(card)
>>> hand.add_card(card)
>>> print(hand)
>>> print(hand)
King of Spades
```
A natural next step is to encapsulate this code in a method called move_cards:
> 接下來很自然地,我們把這段名為 move_cards 的方法放進去:
```Python
#inside class Deck:
def move_cards(self, hand, num):
for i in range(num):
hand.add_card(self.pop_card())
```
move_cards takes two arguments, a Hand object and the number of cards to deal. It modifies both self and hand, and returns None.
> move_cards 方法接收兩個參數,一個 Hand 對象,以及一個要處理的紙牌數量。該方法會修改 self 和 hand。返回為空。
In some games, cards are moved from one hand to another, or from a hand back to the deck. You can use move_cards for any of these operations: self can be either a Deck or a Hand, and hand, despite the name, can also be a Deck.
> 在有的游戲中,紙牌需要從一手牌拿出去放到另外一手牌中去,或者從手中拿出去放到牌堆里面。這就虧用 move_cards 來實現這些操作:第一個變量 self 可以是一副牌也可以是一手牌,第二個變量雖然名字是 hand,實際上也可以是一個 Deck 對象。
Inheritance is a useful feature. Some programs that would be repetitive without inheritance can be written more elegantly with it. Inheritance can facilitate code reuse, since you can customize the behavior of parent classes without having to modify them. In some cases, the inheritance structure reflects the natural structure of the problem, which makes the design easier to understand.
> 繼承是一個很有用的功能。有的程序如果不用繼承的話就會有很多重復代碼,用繼承來寫出來就會更簡潔很多了。繼承有助于代碼重用,因為你可以對基類的行為進行定制而不用去修改基類本身。在某些情況下,繼承的結構也反映了要解決的問題中的自然關系,這就讓程序設計更易于理解。
On the other hand, inheritance can make programs difficult to read. When a method is invoked, it is sometimes not clear where to find its definition. The relevant code may be spread across several modules. Also, many of the things that can be done using inheritance can be done as well or better without it.
> 然而繼承也容易降低程序可讀性。當調用一個方法的時候,有時候不容易找到該方法的定義位置。相關的代碼可能跨了好幾個模塊。此外,很多事情可以用繼承來實現,但不用繼承也能做到同樣效果,甚至做得更好。
## 18.8 Class diagrams 類圖
So far we have seen stack diagrams, which show the state of a program, and object diagrams, which show the attributes of an object and their values. These diagrams represent a snapshot in the execution of a program, so they change as the program runs.
> 目前為止,我們見過棧圖了,棧圖是展示一個程序的狀態的,我們還見過對象圖了,表示的是一個對象中的各個屬性及其值。這些圖都是對一個程序運行中某個瞬間的反映,因此隨著程序運行而產生變化。
They are also highly detailed; for some purposes, too detailed. A class diagram is a more abstract representation of the structure of a program. Instead of showing individual objects, it shows classes and the relationships between them.
> 這些圖解還都非常詳細;有的時候就都過于繁瑣冗余了。而類圖則是對一個程序結構的更抽象的表示。類圖并不會表現出各個獨立的對象,而是會表現出程序中的各個類以及它們之間的關系。
There are several kinds of relationship between classes:
> 類之間有很多種關系,大概如下所示:
? Objects in one class might contain references to objects in another class. For example, each Rectangle contains a reference to a Point, and each Deck contains references to many Cards. This kind of relationship is called HAS-A, as in, “a Rectangle has a Point.”
> 一個類中的對象可能包含了另一個類對象的引用。例如,每一個 Rectangle (矩形)對象都包含了對 Point(點)的引用,而每一個 Deck (成副的牌)對象都包含了對很多個 Card (紙牌)對象的引用。這種關系也叫做『含有』,就好比是說,『一個矩形中含有一個點。』
? One class might inherit from another. This relationship is called IS-A, as in, “a Hand is a kind of a Deck.”
> 一類可能繼承了其他的類。這種關系也可以叫做『是一個』,比如說,『一手牌就是一種牌的組合。』
? One class might depend on another in the sense that objects in one class take objects in the second class as parameters, or use objects in the second class as part of a computation. This kind of relationship is called a dependency.
> 一種類可能要依賴其他類,比如一個類中的對象用另外一個類中的對象作為參數,或者用做計算中的某一部分。這種關系就叫做『依賴』。
A class diagram is a graphical representation of these relationships. For example, Figure 18.2 shows the relationships between Card, Deck and Hand.
> 類圖就是對這些關系的一個圖形化的表示。比如,在途18.2中,就展示了 Card,Deck 以及 Hand 三個類的關系。
* * *

Figure 18.2: Class diagram.
* * *
The arrow with a hollow triangle head represents an IS-A relationship; in this case it indicates that Hand inherits from Deck.
> 有空心三角形的箭頭表示了『是一個』的關系;在這里意思就是 Hand 繼承了 Deck。
The standard arrow head represents a HAS-A relationship; in this case a Deck has references to Card objects.
> 另一個箭頭表示了『有一個』的關系;在這里的意思是 Deck 當中有若干對 Card 對象的引用。
The star (*) near the arrow head is a multiplicity; it indicates how many Cards a Deck has. A multiplicity can be a simple number, like 52, a range, like 5..7 or a star, which indicates that a Deck can have any number of Cards.
> 箭頭處有個小星號*;這里可以表明一個 Deck 中含有的 Card的個數。可以標出個數,比如52,或者是范圍,比如5..7或者一個星號,這就意味著一個 Deck 中可以含有任意個數的 Card。
There are no dependencies in this diagram. They would normally be shown with a dashed arrow. Or if there are a lot of dependencies, they are sometimes omitted.
> 這個圖解中沒有出現依賴關系。這種關系一般用虛線箭頭來表示。或者當依賴關系很多的時候,有時候就都忽略掉了。
A more detailed diagram might show that a Deck actually contains a list of Cards, but built-in types like list and dict are usually not included in class diagrams.
> 更細節化的圖解就可能表現出一個 Deck 中會包含一個 Card 對象組成的列表,但一般情況下類圖不會包括內置類型比如列表和字典。
## 18.9 Debugging 調試
Inheritance can make debugging difficult because when you invoke a method on an object, it might be hard to figure out which method will be invoked.
> 繼承可以讓調試變得很夸你呢,因為你調用某個對象中的某個方法的時候,很難確定到底是調用的哪一個方法。
Suppose you are writing a function that works with Hand objects. You would like it to work with all kinds of Hands, like PokerHands, BridgeHands, etc. If you invoke a method like shuffle, you might get the one defined in Deck, but if any of the subclasses override this method, you’ll get that version instead. This behavior is usually a good thing, but it can be confusing.
> 假設你寫一個處理 Hand 對象的函數。你可能要讓該函數適用于所有類型的牌型,比如常規牌型,橋牌牌型等等。假設你要調用洗牌的方法 shuffle,你可能用的是 Deck 類當中的,不過如果子類當中有覆蓋的該方法,你運行的就是子類中的方法了。這種行為一般是很有好處的,不過也容易把人弄糊涂。
Any time you are unsure about the flow of execution through your program, the simplest solution is to add print statements at the beginning of the relevant methods. If Deck.shuffle prints a message that says something like Running Deck.shuffle, then as the program runs it traces the flow of execution.
> 在你的程序運行的過程中,只要你對程序流程有疑問了,就可以在相關的方法頭部添加print 語句來打印輸出一下信息,這就是最簡單的解決方法了。如果 Deck.shuffle 輸出了信息比如說『在運行 Deck 的 shuffle』,那就可以根據這些信息來追蹤執行流程了。
As an alternative, you could use this function, which takes an object and a method name (as a string) and returns the class that provides the definition of the method:
> 另外一個思路,就是用下面這個函數,該函數接收一個對象和一個方法的名字(作為字符串),然后返回提供該方法定義的類的名稱。
```Python
def find_defining_class(obj, meth_name):
for ty in type(obj).mro():
if meth_name in ty.__dict__:
return ty
```
Here’s an example:
> 如下所示:
```Python
>>> hand = Hand()
>>> hand = Hand()
>>> find_defining_class(hand, 'shuffle')
>>> find_defining_class(hand, 'shuffle')
<class 'Card.Deck'>
```
So the shuffle method for this Hand is the one in Deck.
find_defining_class uses the mro method to get the list of class objects (types) that will be searched for methods. “MRO” stands for “method resolution order”, which is the sequence of classes Python searches to “resolve” a method name.
> 所這樣就能判斷這里面 Hand 中的 shuffle 方法是來自 Deck 的。
> find_defining_class 用了 mro 方法來獲取所有搜索方法的類對象的列表。
> 『MRO』的意思是『method resolution order(方法 解決方案 順序)』,也就是 Python 搜索來找到方法名的類的序列。
Here’s a design suggestion: when you override a method, the interface of the new method should be the same as the old. It should take the same parameters, return the same type, and obey the same preconditions and postconditions. If you follow this rule, you will find that any function designed to work with an instance of a parent class, like a Deck, will also work with instances of child classes like a Hand and PokerHand.
> 下面是一個在設計上的建議:當你覆蓋一個方法的時候,新的方法的接口最好同舊的完全一致。應該接收同樣的參數,返回同樣類型,并且遵循同樣的前置條件和后置條件。只要你遵守這個規則,你就會發現所有之前設計來處理一個基類的函數,比如處理 Deck 的,就都可以用于子類的實例上面,比如 Hand 類或者 PokerHand 類。
If you violate this rule, which is called the “Liskov substitution principle”, your code will collapse like (sorry) a house of cards.
> 如果你違背了上面這個『里氏替換原則』,你的代碼就可能很悲劇地崩潰,就像無數紙牌坍塌一樣。
## 18.10 Data encapsulation 數據封裝
The previous chapters demonstrate a development plan we might call “object-oriented design”. We identified objects we needed—like Point,Rectangle and Time—and defined classes to represent them. In each case there is an obvious correspondence between the object and some entity in the real world (or at least a mathematical world).
> 之前的章節中,我們展示了所謂『面向對象設計』的開發規劃模式。在這些章節中,我們顯示確定好需要的對象—比如點,矩形以及時間—然后再定義一些類去代表這些內容。在這些例子中,類的對象與現實世界(或者至少是數學世界)中的一些實體都有顯著的對應關系。
But sometimes it is less obvious what objects you need and how they should interact. In that case you need a different development plan. In the same way that we discovered function interfaces by encapsulation and generalization, we can discover class interfaces by data encapsulation.
> 不過有時候就不太好確定具體需要什么樣的對象,以及如何去實現。這時候就需要一種完全不同的開發規劃模式了。之前我們對函數接口進行過封裝和泛化的處理,現在也可以通過數據封裝來改進類的接口。
Markov analysis, from Section 13.8, provides a good example. If you download my code from [Here](http://thinkpython2.com/code/markov.py) , you’ll see that it uses two global variables—suffix_map and prefix—that are read and written from several functions.
> 比如馬科夫分析,在13.8中出現的,就是一個很好的例子。如果你從[這里](http://thinkpython2.com/code/markov.py)下載了我的樣例代碼,你就會發現這里用了兩個全局變量—suffix_map 以及 prefix—這兩個全局變量會被多個函數讀取和寫入。
```Python
suffix_map = {}
prefix = ()
```
Because these variables are global, we can only run one analysis at a time. If we read two texts, their prefixes and suffixes would be added to the same data structures (which makes for some interesting generated text).
> 這些變量是全局的,因此我們每次只運行了一次分析。如果我們要讀取兩個文本,他們的前置和后置詞匯都會被添加到同樣的數據結構上面去(這樣就能生成一些有趣的機器制造的文本了)。
To run multiple analyses, and keep them separate, we can encapsulate the state of each analysis in an object. Here’s what that looks like:
> 如果要運行多次分析,并且要對這些分析進行區分,我們可以把每次分析的狀態封裝到對象中。如下所示:
```Python
class Markov:
def __init__(self):
self.suffix_map = {}
self.prefix = ()
```
Next, we transform the functions into methods. For example, here’s process_word:
> 接下來就是把各個函數轉換成方法。例如下面就是process_word 方法:
```Python
def process_word(self, word, order=2):
if len(self.prefix) < order:
self.prefix += (word,)
return
try:
self.suffix_map[self.prefix].append(word)
except
KeyError: # if there is no entry for this prefix, make one
self.suffix_map[self.prefix] = [word]
self.prefix = shift(self.prefix, word)
```
Transforming a program like this—changing the design without changing the behavior—is another example of refactoring (see Section 4.7).
> 上面這種方式對程序進行的修改只是改變了設計,而不改變程序的行為,這就是重構的另一個例子(參考4.7)。
This example suggests a development plan for designing objects and methods:
> 這一樣例展示了一種設計累的對象和方法的開發規劃模式:
1. Start by writing functions that read and write global variables (when necessary).
> 先開始寫一些函數來讀去和寫入全局變量(在必要的情況下)。
2. Once you get the program working, look for associations between global variables and the functions that use them.
> 一旦程序可以工作了,就檢查一下全局變量與使用它們的函數之間的關系。
3. Encapsulate related variables as attributes of an object.
> 把相關的變量作為類的屬性封裝到一起。
4. Transform the associated functions into methods of the new class.
> 把相關的函數轉換成新類的方法。
As an exercise, download my Markov code from [Here](http://thinkpython2.com/code/markov.py), and follow the steps described above to encapsulate the global variables as attributes of a new class called Markov. [Solution](http://thinkpython2.com/code/Markov.py) (note the capital M).
> 做一個練習,從[這里](http://thinkpython2.com/code/markov.py)下載我的馬科夫分析代碼,然后根據上面說的步驟來一步步把全局變量封裝成一個名為 Markov 的新類的屬性。[樣例代碼](http://thinkpython2.com/code/Markov.py) (一定要注意 M 是大寫的哈)
## 18.11 Glossary 術語列表
encode:
To represent one set of values using another set of values by constructing a mapping between them.
> 編碼:通過建立映射的方式來用一系列的值來表示另外一系列的值。
class attribute:
An attribute associated with a class object. Class attributes are defined inside a class definition but outside any method.
> 類的屬性:屬于某個類的對象的屬性。類的屬性都在類定義的內部,在類內方法之外。
instance attribute:
An attribute associated with an instance of a class.
> 實例屬性:屬于某個類的實例的屬性。
veneer:
A method or function that provides a different interface to another function without doing much computation.
> 嵌板:某一方法或者函數,為另外的函數提供了不同的界面,而沒有做額外運算。
inheritance:
The ability to define a new class that is a modified version of a previously defined class.
> 繼承:基于已定義過的類,進行修改來定義一個新類,這種特性就是繼承。
parent class:
The class from which a child class inherits.
> 基類:被子類繼承的類。
child class:
A new class created by inheriting from an existing class; also called a “subclass”.
> 子類:基于已有類而建立的新類;也稱為『分支類』。
IS-A relationship:
A relationship between a child class and its parent class.
> 『是一個』關系:一種子類與基類之間的關系。
HAS-A relationship:
A relationship between two classes where instances of one class contain references to instances of the other.
> 『有一個』關系:某一個類的實例中包含其他類的實例的引用的關系。
dependency:
A relationship between two classes where instances of one class use instances of the other class, but do not store them as attributes.
> 依賴關系:兩個類之間的一種關系,一個類的實例使用了另外一個類的實例,但并未作為屬性來存儲。
class diagram:
A diagram that shows the classes in a program and the relationships between them.
> 類圖:一種展示程序中各個類及其之間關系的圖解。
multiplicity:
A notation in a class diagram that shows, for a HAS-A relationship, how many references there are to instances of another class.
> 多樣性:類圖中顯示的一種記號,適用于『有一個』關系中,表示一個類當中另一個類的實例的引用的個數。
data encapsulation:
A program development plan that involves a prototype using global variables and a final version that makes the global variables into instance attributes.
> 數據封裝:一種程序開發規劃方式,用全局變量做原型體,然后逐步將這些全局變量轉換成實例的屬性。
## 18.12 Exercises 練習
### Exercise 1 練習1
For the following program, draw a UML class diagram that shows these classes and the relationships among them.
> 閱讀下面的代碼,畫一個 UML 類圖,表示出程序中的類,以及類之間的關系。
```Python
class PingPongParent:
pass class Ping(PingPongParent):
def __init__(self, pong):
self.pong = pong
class Pong(PingPongParent):
def __init__(self, pings=None):
if pings is None:
self.pings = []
else:
self.pings = pings
def add_ping(self, ping):
self.pings.append(ping)
pong = Pong()
ping = Ping(pong)
pong.add_ping(ping)
```
### Exercise 2 練習2
Write a Deck method called deal_hands that takes two parameters, the number of hands and the number of cards per hand. It should create the appropriate number of Hand objects, deal the appropriate number of cards per hand, and return a list of Hands.
> 為 Deck 類寫一個名為 deal_hands 的方法,接收兩個參數,一個為牌型數量,一個為每一個牌型的紙牌數。該方法需要創建適當的牌型對象數量,處理適當的每個牌型中的紙牌數,然后返回一個牌型組成的列表。
### Exercise 3 練習3
The following are the possible hands in poker, in increasing order of value and decreasing order of probability:
> 下面是撲克牌中可能的各個牌型,排列順序為值的升序,出現概率的降序:
pair:
two cards with the same rank
> 一對:兩張同樣牌值的牌
two pair:
two pairs of cards with the same rank
> 雙對:兩對同樣牌值的牌
three of a kind:
three cards with the same rank
> 三張:三張同樣牌值的牌
straight:
five cards with ranks in sequence (aces can be high or low, so Ace-2-3-4-5 is a straight and so is 10-Jack-Queen-King-Ace, but Queen-King-Ace-2-3 is not.)
> 順子:五張牌值連續的牌(A 可以用作開頭,也可以用作結尾,所以 A-2-3-4-5是一個順子,10-J-Q-K-A 也是一個,但 Q-K-A-2-3就不行了。)
flush:
five cards with the same suit
> 同花:五張牌花色一致
full house:
three cards with one rank, two cards with another
> 三帶二:三張同牌值的牌,兩張另外的同牌值的牌
four of a kind:
four cards with the same rank
> 四條:四張同一牌值的牌
straight flush:
five cards in sequence (as defined above) and with the same suit
> 同花順:五張組成順子并且是同一花色的牌
The goal of these exercises is to estimate the probability of drawing these various hands.
> 此次練習的目的就是要估計獲得以上各個牌型的概率。
1. Download the following files from [Here](http://thinkpython2.com/code)
> 從[這個網址](http://thinkpython2.com/code)下載下面的文件:
:Card.py
: A complete version of the Card, Deck and Hand classes in this chapter.
> 該文件是本章所涉及的 Card,Deck 以及 Hand 類的完整實現。
PokerHand.py
: An incomplete implementation of a class that represents a poker hand, and some code that tests it.
> 該文件是一個不完整版本的類,表示的是一個牌型,以及一些測試代碼。
2. If you run PokerHand.py, it deals seven 7-card poker hands and checks to see if any of them contains a flush. Read this code carefully before you go on.
> 如果你運行 PokerHand.py,改程序會處理七個七張牌的牌型,然后檢查是否其中包含一副順子。
> 好好閱讀一下這份代碼,然后再繼續后面的練習。
3. Add methods to PokerHand.py named has_pair, has_twopair, etc. that return True or False according to whether or not the hand meets the relevant criteria. Your code should work correctly for “hands” that contain any number of cards (although 5 and 7 are the most common sizes).
> 在 PokerHand.py 里面增加名為has_pair, has_twopair等等方法。這些方法根據牌型中是否滿足特定的組合而返回 True 或者 False。你的代碼應該能適用于有任意張牌的牌型(雖然5或者7是最常見的牌數)。
4. Write a method named classify that figures out the highest-value classification for a hand and sets the label attribute accordingly. For example, a 7-card hand might contain a flush and a pair; it should be labeled “flush”.
> 寫一個名為 classify 的函數,判斷出一副牌型中的最高值的一份,然后用來命名到標簽屬性。例如,一個七張牌的牌型可能包含一個順子和一個對子;這就應該被標為『順子』。
5. When you are convinced that your classification methods are working, the next step is to estimate the probabilities of the various hands. Write a function in PokerHand.py that shuffles a deck of cards, divides it into hands, classifies the hands, and counts the number of times various classifications appear.
> 當你確定你的分類方法運轉正常了,下一步就是要估計各個牌型的出現概率。在 PokerHand.py 中寫一個函數來對一副牌進行洗牌,分成多個牌型,對各個牌型進行分類,然后統計不同類型出現的次數。
6. Print a table of the classifications and their probabilities. Run your program with larger and larger numbers of hands until the output values converge to a reasonable degree of accuracy. Compare your results to the values at [Here](http://en.wikipedia.org/wiki/Hand_rankings).
> 打印輸出一個由類型和概率組成的列表。逐步用大規模的牌型來測試你的程序,直到輸出的值趨向于一個比較合理的準確范圍。把你的運行結果與[這里的結果](http://en.wikipedia.org/wiki/Hand_rankings)進行對比。
[Solution](http://thinkpython2.com/code/PokerHandSoln.py)
[樣例代碼](http://thinkpython2.com/code/PokerHandSoln.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 額外補充