← 学习库 Think Python (2e) · 中英对照 目录

Chapter 18  Inheritance 第 18 章 继承

本页译自 Think Python 2e(Allen B. Downey)· Chapter 18 Inheritance。代码块保留英文原文不翻译;正文段段对照,中文块可用右下角按钮隐藏。

In this chapter I present classes to represent playing cards, decks of cards, and poker hands. If you don't play poker, you can read about it at http://en.wikipedia.org/wiki/Poker, but you don't have to; I'll tell you what you need to know for the exercises. Code examples from this chapter are available from http://thinkpython.com/code/Card.py.

本章我介绍一些类,用来表示扑克牌、整副牌组以及一手扑克牌(poker hands)。如果你不玩扑克,可以去 http://en.wikipedia.org/wiki/Poker 了解,但没必要;习题需要的前置知识我会在这里讲清楚。本章的代码示例可以从 http://thinkpython.com/code/Card.py 获取。

If you are not familiar with Anglo-American playing cards, you can read about them at http://en.wikipedia.org/wiki/Playing_cards.

如果你不熟悉英美扑克牌,可以去 http://en.wikipedia.org/wiki/Playing_cards 了解一下。

18.1 Card objects 18.1 卡片对象

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.

一副牌有五十二张,每张牌属于一种花色(suit)和一个点数(rank)。花色有黑桃、红心、方块和梅花四种(在桥牌里按降序排列)。点数有 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 rank. It is not as obvious what type the attributes should be. One possibility is to use strings containing words like rank000 for suits and rank000 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.

如果想定义一个新对象来表示一张扑克牌,该有哪些属性是很清楚的:rank(点数)和 rank(花色)。但这些属性该用什么类型就不那么清楚了。一种做法是使用字符串,比如用 rank000 表示花色、rank000 表示点数。这种做法有个问题:不容易比较两张牌,看谁的等级或花色更高。

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").

另一种做法是用整数来编码(encode)点数和花色。这里的「编码」是指我们要在数值和花色之间、或者在数值和点数之间定义一套映射。这种编码并不是为了保密(那叫「加密」)。

For example, this table shows the suits and the corresponding integer codes:

例如,下表给出了花色以及对应的整数编码:
SuitCode
Spades3
Hearts2
Diamonds1
Clubs0
花色编码
黑桃3
红心2
方块1
梅花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:

点数的映射相当直观:每个数字点数都映射到对应的整数,而人头牌的映射如下:
RankCode
Jack11
Queen12
King13
点数编码
杰克11
皇后12
国王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 rank looks like this:

rank 的类定义如下:
rank00020

As usual, the init method takes an optional parameter for each attribute. The default card is the 2 of Clubs.

和往常一样,init 方法为每个属性都提供了一个可选参数。默认的牌是梅花 2。

To create a Card, you call rank with the suit and rank of the card you want.

要创建一张牌,你用想要的花色和点数调用 rank
rank00023

18.2 Class attributes 18.2 类属性

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:

为了让 rank 对象打印出来便于人阅读,我们需要一套从整数编码到对应点数和花色的映射。一个自然的做法是使用字符串列表,并把这些列表赋值给类属性(class attribute):
rank00025

Variables like rank00026 and rank00027, which are defined inside a class but outside of any method, are called class attributes because they are associated with the class object rank.

rank00029 和 rank00030 这种定义在类内部、但方法之外的变量,被称为类属性,因为它们是与类对象 rank 关联在一起的。

This term distinguishes them from variables like rank and rank, which are called instance attributes because they are associated with a particular instance.

这个术语用来把它们和 rankrank 这类变量区分开,后者被称为实例属性(instance attribute),因为它们是与某个具体实例关联的。

Both kinds of attribute are accessed using dot notation. For example, in rank000, rank is a Card object, and rank00038 is its rank. Similarly, rank is a class object, and rank00040 is a list of strings associated with the class.

两种属性都用点记法访问。例如在 rank000 里,rank 是一个 rank 对象,rank00044 是它的点数;同理,rank 是一个类对象,rank00046 是与该类关联的字符串列表。

Every card has its own rank and rank, but there is only one copy of rank00049 and rank00050.

每张牌都有自己的 rankrank,但 rank00053 和 rank00054 只有一份。

Putting it all together, the expression rank00055 means "use the attribute rank from the object rank as an index into the list rank00058 from the class rank, and select the appropriate string."

综合起来,表达式 rank00060 的意思是「用对象 rankrank 属性作为索引,去查类 rankrank00064 列表,取出对应的字符串」。

The first element of rank00065 is rank because there is no card with rank zero. By including rank 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.

rank00069 的第一个元素是 rank,因为没有点数为零的牌。把 rank 作为一个占位符,我们就得到了一个很方便的映射:索引 2 恰好对应字符串 '2',依此类推。如果不想做这种微调,也可以用字典代替列表。

With the methods we have so far, we can create and print cards:

有了目前为止的方法,我们就可以创建并打印牌了:
rank00073

Figure 18.1: Object diagram.

图 18.1:对象图(原书插图未收录)

Figure 18.1 is a diagram of the rank class object and one Card instance. rank is a class object, so it has type rank. rank0 has type rank. (To save space, I didn't draw the contents of rank00079 and rank00080).

图 18.1 是 rank 类对象和一个 rank 实例的图示。rank 是类对象,所以它的类型是 rankrank0 的类型是 rank。(为了节省空间,我没有画出 rank00087 和 rank00088 的内容。)

18.3 Comparing cards 18.3 比较牌

For built-in types, there are relational operators (rank, rank, ==, etc.) that compare values and determine when one is greater than, less than, or equal to another. For user-defined types, we can override the behavior of the built-in operators by providing a method named rank000.

内置类型有比较运算符(rankrank== 等),用来比较值的大小、判断谁大谁小或相等。对于自定义类型,我们可以通过提供一个名为 rank000 的方法来覆写这些内置运算符的行为。

rank000 takes two parameters, rank and rank0, and returns a positive number if the first object is greater, a negative number if the second object is greater, and 0 if they are equal to each other.

rank001 接收两个参数 rankrank0;如果第一个对象更大则返回正数,如果第二个对象更大则返回负数,如果两者相等则返回 0。

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.

牌的正确排序并不显然。比如,梅花 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 rank001:

定下这条规则后,就可以写出 rank001 了:
rank00105

You can write this more concisely using tuple comparison:

利用元组比较,可以把代码写得更简洁:
rank00106

The built-in function '2' has the same interface as the method rank001: it takes two values and returns a positive number if the first is larger, a negative number if the second is larger, and 0 if they are equal.

内置函数 '2' 和方法 rank001 的接口相同:它接收两个值,若第一个更大返回正数,若第二个更大返回负数,相等则返回 0。

In Python 3, '2' no longer exists, and the rank001 method is not supported. Instead you should provide rank00, which returns rank if rank is less than rank0. You can implement rank00 using tuples and the rank operator.

在 Python 3 中,'2' 已经不存在,rank001 方法也不再支持。你应该改用 rank00,当 rank 小于 rank0 时返回 rank。可以用元组和 rank 运算符来实现 rank00。

Exercise 1

习题 1

Write a rank001 method for Time objects. Hint: you can use tuple comparison, but you also might consider using integer subtraction.

为 Time 对象写一个 rank001 方法。提示:可以用元组比较,也可以考虑用整数相减。

18.4 Decks 18.4 牌组

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.

有了 Card,下一步就是定义 Deck(牌组)。既然一副牌由多张牌组成,那么很自然,每个 Deck 用一个牌的列表作为属性。

The following is a class definition for rank. The init method creates the attribute rank0 and generates the standard set of fifty-two cards:

下面是 rank 的类定义。init 方法创建 rank0 属性,并生成标准的一副五十二张牌:
rank00133

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

填充牌组最简单的办法是用嵌套循环。外层循环枚举花色 0 到 3,内层循环枚举点数 1 到 13。每轮迭代都用当前的花色和点数创建一张新牌,并追加到 rank00135。

18.5 Printing the deck 18.5 打印牌组

Here is a rank001 method for rank:

下面是 rank 的一个 rank001 方法:
rank00140

This method demonstrates an efficient way to accumulate a large string: building a list of strings and then using rank. The built-in function '2' invokes the rank001 method on each card and returns the string representation.

这个方法展示了一种高效累积大字符串的做法:先构建一个字符串列表,再用 rank 拼接。内置函数 '2' 会对每张牌调用它的 rank001 方法,并返回其字符串表示。

Since we invoke rank on a newline character, the cards are separated by newlines. Here's what the result looks like:

因为我们是用换行符来调用 rank 的,所以各张牌之间以换行分隔。结果看起来像这样:
rank00149

Even though the result appears on 52 lines, it is one long string that contains newlines.

虽然结果显示成 52 行,但它其实是一个包含换行符的长字符串。

18.6 Add, remove, shuffle and sort 18.6 加牌、删牌、洗牌与排序

To deal cards, we would like a method that removes a card from the deck and returns it. The list method '2' provides a convenient way to do that:

要发牌,我们需要一个从牌组里抽出一张牌并返回的方法。列表的 '2' 方法正好提供了方便的做法:
rank00152

Since '2' removes the last card in the list, we are dealing from the bottom of the deck. In real life "bottom dealing" is frowned upon, but in this context it's ok.

因为 '2' 删掉的是列表里的最后一张牌,所以我们的发牌是从牌组底部发的。现实生活中「从底部发牌」为人所不齿,但在这里没关系。

To add a card, we can use the list method rank00:

要加一张牌,可以用列表的 rank00 方法:
rank00157

A method like this that uses another function without doing much real work is sometimes called a veneer. The metaphor comes from woodworking, where it is common to glue a thin layer of good quality wood to the surface of a cheaper piece of wood.

这种调用了别的函数、自己没做多少实质工作的方法,有时被称为薄封装(veneer,转接层)。这个比喻来自木工:人们常把一层薄薄的优质木料粘在廉价木料表面。

In this case we are defining a "thin" method that expresses a list operation in terms that are appropriate for decks.

在这里,我们定义的这个「薄」方法,用对牌组而言合适的说法来表达一次列表操作。

As another example, we can write a Deck method named rank001 using the function rank001 from the rank00 module:

再举一例,我们可以用 rank00 模块的 rank001 函数,给 Deck 写一个名为 rank001 的方法:
rank00164

Don't forget to import rank00.

别忘了导入 rank00。

Exercise 2

习题 2

Write a Deck method named rank that uses the list method rank to sort the cards in a rank. rank uses the rank001 method we defined to determine sort order.

写一个 Deck 方法 rank,用列表方法 rankrank 里的牌排序。rank 会用到我们定义的 rank001 方法来确定顺序。

18.7 Inheritance 18.7 继承

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.

面向对象编程中最常被提及的语言特性是继承(inheritance)。继承是指:能够定义一个新类,它是某个已有类的修改版。

It is called "inheritance" because the new class inherits the methods of the existing class. Extending this metaphor, the existing class is called the parent and the new class is called the child.

它之所以叫「继承」,是因为新类继承了已有类的方法。顺着这个比喻,已有的类叫做父类(parent),新类叫做子类(child)。

As an example, let's say we want a class to represent a "hand," that is, the set of cards held by one player. A hand is similar to a deck: both are made up of a set 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.

类之间这种「相似却又不同」的关系,特别适合用继承来表达。

The definition of a child class is like other class definitions, but the name of the parent class appears in parentheses:

子类的写法和其它类定义差不多,只是父类的名字要放在括号里:
rank00177

This definition indicates that rank inherits from rank; that means we can use methods like rank0018 and rank0018 for Hands as well as Decks.

这个定义表明 rank 继承自 rank;也就是说,rank0018、rank0018 这些方法既能用于 Deck,也能用于 Hand。

rank also inherits rank0018 from rank, 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 rank0 with an empty list.

rank 还从 rank 继承了 rank0019,但它并没有做我们想要的事:Hand 的 init 方法不该装入 52 张新牌,而应该用一个空列表来初始化 rank0。

If we provide an init method in the rank class, it overrides the one in the rank class:

如果在 rank 类里提供一个 init 方法,它就会覆盖 rank 类里的那个:
rank00198

So when you create a Hand, Python invokes this init method:

所以当你创建一个 Hand 时,Python 会调用这个 init 方法:
rank00199

But the other methods are inherited from rank, so we can use rank0020 and rank0020 to deal a card:

但其它方法是从 rank 继承来的,所以我们可以用 rank0020 和 rank0020 来发出一张牌:
rank00206

A natural next step is to encapsulate this code in a method called rank00207:

很自然的一步,是把这段代码封装进一个叫 rank00208 的方法里:
rank00209

rank00210 takes two arguments, a Hand object and the number of cards to deal. It modifies both rank and rank, and returns rank.

rank00214 接收两个参数:一个 Hand 对象,以及要发的牌数。它会同时修改 rankrank,并返回 rank

In some games, cards are moved from one hand to another, or from a hand back to the deck. You can use rank00218 for any of these operations: rank can be either a Deck or a Hand, and rank, despite the name, can also be a rank.

有些游戏里,牌会从一个手牌移到另一个手牌,或者从手牌移回牌组。这些操作你都可以用 rank00222:rank 既可以是 Deck 也可以是 Hand,而 rank 这个名字虽叫 hand,其实也可以是 rank

Exercise 3

习题 3

Write a Deck method called rank00226 that takes two parameters, the number of hands and the number of cards per hand, and that creates new Hand objects, deals the appropriate number of cards per hand, and returns a list of Hand objects.

写一个 Deck 方法 rank00227,接收两个参数:手牌数量和每手牌的张数;它创建新的 Hand 对象,给每手发出相应数量的牌,并返回 Hand 对象组成的列表。

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 program 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 scattered among 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 18.8 类图

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:

类之间有多种关系:

A class diagram is a graphical representation of these relationships. For example, Figure 18.2 shows the relationships between rank, rank and rank.

类图(class diagram)就是这些关系的图形化表示。例如,图 18.2 展示了 rankrankrank 之间的关系。

Figure 18.2: Class diagram.

图 18.2:类图(原书插图未收录)

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 ==, a range, like rank or a star, which indicates that a Deck can have any number of Cards.

箭头附近的星号(*)是多重性(multiplicity),表示一副牌有若干个 Card。多重性可以是一个简单的数(如 ==)、一个范围(如 rank),或者一个星号,表示一副牌可以有任意数量的 Card。

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 的列表,但像 list、dict 这类内置类型通常不会出现在类图里。

Exercise 4

习题 4

Read rank00240, rank0024 and rank00 and draw a class diagram that shows the relationships among the classes defined there.

阅读 rank00243、rank0024 和 rank00,画一张类图,展示其中定义的那些类之间的关系。

18.9 Debugging 18.9 调试

Inheritance can make debugging a challenge because when you invoke a method on an object, you might not know 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 rank002, you might get the one defined in rank, but if any of the subclasses override this method, you'll get that version instead.

假设你正在写一个处理 Hand 对象的函数,你希望它能适用于各种 Hand,比如 PokerHand、BridgeHand 等。如果你调用 rank002 这样的方法,得到的可能是 rank 里定义的那个,但只要有任何子类重写了它,你拿到的就是子类的版本。

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 rank00250 prints a message that says something like rank00251, then as the program runs it traces the flow of execution.

每当你对程序的执行流程没把握时,最简单的办法是在相关方法的开头加上 print 语句。如果 rank00252 打印出类似 rank00253 的信息,那么程序运行时就会把执行流程追踪出来。

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:

另一种办法是用下面这个函数,它接收一个对象和一个方法名(字符串形式),返回提供该方法定义的那个类:
rank00254

Here's an example:

下面是一个例子:
rank00255

So the rank002 method for this Hand is the one in rank.

所以这个 Hand 的 rank002 方法,就是 rank 里那个。

rank00260 uses the '2' method to get the list of class objects (types) that will be searched for methods. "MRO" stands for "method resolution order."

rank00262 用 '2' 方法获取将被搜索的类对象(类型)列表。「MRO」是「method resolution order(方法解析顺序)」的缩写。

Here's a program design suggestion: whenever 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 obey this rule, you will find that any function designed to work with an instance of a superclass, like a Deck, will also work with instances of subclasses like a Hand or PokerHand.

这里有一条程序设计建议:每当你重写一个方法时,新方法的接口应该和旧方法一致。它应当接收同样的参数、返回同样的类型,并且遵守同样的先决条件和后置条件。遵守这条规则,你会发现任何为超类(比如 Deck)实例设计的函数,也同样能用于 Hand、PokerHand 这样的子类实例。

If you violate this rule, your code will collapse like (sorry) a house of cards.

如果你违反这条规则,你的代码就会(抱歉)像纸牌屋一样塌掉。

18.10 Data encapsulation 18.10 数据封装

Chapter 16 demonstrates a development plan we might call "object-oriented design." We identified objects we needed—rank, rank0 and rank00266—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).

第 16 章展示了一种我们不妨称为「面向对象设计」的开发方案。我们识别出需要的对象——rankrank0 和 rank00269——并定义类来表示它们。每种情况下,对象与现实世界(或至少是数学世界)中的某个实体都有明显的对应关系。

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.

但有时你需要什么对象、它们该如何交互并不那么明显。这时你就得换一套开发方案。正如我们当初通过封装(encapsulation)和泛化(generalization)发现了函数的接口,我们也可以通过数据封装(data encapsulation)来发现类的接口。

Markov analysis, from Section 13.8, provides a good example. If you download my code from rank00270, you'll see that it uses two global variables—rank00271 and rank00—that are read and written from several functions.

第 13.8 节的马尔可夫分析就是一个好例子。如果你从 rank00273 下载我的代码,会发现它用了两个全局变量——rank00274 和 rank00——被好几个函数读写。
rank00276

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

因为这些变量是全局的,所以我们一次只能运行一个分析。如果读入两段文本,它们的 prefixes 和 suffixes 会被加到同一份数据结构里(这会生成一些很有意思的文本)。

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:

要同时运行多个分析并让它们互不干扰,我们可以把每个分析的状态封装进一个对象。做法如下:
rank00277

Next, we transform the functions into methods. For example, here's rank00278:

接下来,把函数改造成方法。例如,下面是 rank00279:
rank00280

Transforming a program like this—changing the design without changing the function—is another example of refactoring (see Section 4.7).

像这样改造程序——改变设计但不改变功能——是另一种重构(refactoring)的例子(见第 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.
  1. 先写一些读写全局变量的函数(必要时)。
  2. 等程序跑通后,寻找全局变量和用到它们的函数之间的关联。
  3. 把相关的变量封装为对象的属性。
  4. 把相关的函数改造成新类的方法。

Exercise 5

习题 5

Download my code from Section 13.8 (rank00281), and follow the steps described above to encapsulate the global variables as attributes of a new class called rank00. Solution: rank00283 (note the capital M).

从第 13.8 节下载我的代码(rank00284),按上面步骤把全局变量封装为新类 rank00 的属性。答案:rank00286(注意 M 是大写)。

18.11 Glossary 18.11 术语表

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:
The relationship between a child class and its parent class.
HAS-A relationship:
The relationship between two classes where instances of one class contain references to instances of the other.
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.
encode 编码:
通过构造两套值之间的映射,用一组值来表示另一组值。
class attribute 类属性:
与类对象关联的属性。类属性定义在类定义内部、但任何方法之外。
instance attribute 实例属性:
与类的某个实例关联的属性。
veneer 薄封装(转接层):
为另一个函数提供不同接口、而本身不做什么计算的方法或函数。
inheritance 继承:
能够定义一个新类,它是先前某类的一个修改版。
parent class 父类:
子类所继承的那个类。
child class 子类:
通过继承已有类而创建的新类;也叫「subclass」。
IS-A relationship 「是一个」关系:
子类与其父类之间的关系。
HAS-A relationship 「有一个」关系:
两个类之间的关系,其中一个类的实例持有对另一个类实例的引用。
class diagram 类图:
展示程序中各个类以及它们之间关系的图。
multiplicity 多重性:
类图中的一种记号,用于「有一个」关系,表示对另一个类的实例有多少个引用。

18.12 Exercises 18.12 习题

Exercise 6

习题 6

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 rank00287 is a straight and so is rank00288, but rank00289 is not.)
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
pair 一对:
两张点数相同的牌
two pair 两对:
两组点数相同的对子
three of a kind 三条:
三张点数相同的牌
straight 顺子:
五张点数连续的牌(A 可大可小,所以 Ace-2-3-4-5 是顺子,10-Jack-Queen-King-Ace 也是,但 Queen-King-Ace-2-3 不是)
flush 同花:
五张花色相同的牌
full house 葫芦:
三张同点、两张另点
four of a kind 四条:
四张点数相同的牌
straight flush 同花顺:
五张点数连续且花色相同的牌

The goal of these exercises is to estimate the probability of drawing these various hands.

这些习题的目标是:估计抓到上述各种牌型的概率。
  1. Download the following files from rank00290:
    rank002
    : A complete version of the rank, rank and rank classes in this chapter.
    rank00295
    : An incomplete implementation of a class that represents a poker hand, and some code that tests it.
  2. If you run rank00296, 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.
  3. Add methods to rank00297 named rank0029, rank00299, 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).
  4. Write a method named rank0030 that figures out the highest-value classification for a hand and sets the rank0 attribute accordingly. For example, a 7-card hand might contain a flush and a pair; it should be labeled "flush".
  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 rank00302 that shuffles a deck of cards, divides it into hands, classifies the hands, and counts the number of times various classifications appear.
  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 rank00303.
  1. rank00304 下载以下文件:
    rank003
    :本章 rankrankrank 三个类的完整版本。
    rank00309
    :一个表示扑克牌型的不完整类实现,以及一段测试它的代码。
  2. 运行 rank00310,它会发七手 7 张的扑克牌,并检查其中是否有同花。继续之前请仔细阅读这段代码。
  3. rank00311 添加名为 rank0031、rank00313 等方法,根据手牌是否符合相应条件返回 True 或 False。你的代码应能对包含任意张数的「手牌」正确工作(尽管 5 张和 7 张最常见)。
  4. 写一个名为 rank0031 的方法,找出一手牌价值最高的牌型,并相应地设置 rank0 属性。例如,一手 7 张牌可能同时有同花和一对,此时应标记为「flush」。
  5. 当你确信分类方法已经正确后,下一步是估计各种牌型的概率。在 rank00316 里写一个函数:洗一副牌、把它分成若干手、对每手分类,并统计各种牌型出现的次数。
  6. 打印一张表格,列出各牌型及其概率。用越来越大的手牌数量运行程序,直到输出值收敛到合理精度。把你得到的结果与 rank00317 上的数值做对比。

Solution: rank00318.

答案:rank00319。

Exercise 7

习题 7

This exercise uses TurtleWorld from Chapter 4. You will write code that makes Turtles play tag. If you are not familiar with the rules of tag, see rank00320.

本题使用第 4 章的 TurtleWorld。你要写一段代码,让 Turtle 玩「抓人」游戏。如果你不熟悉抓人规则,参见 rank00321。
  1. Download rank00322 and run it. You should see a TurtleWorld with three Turtles. If you press the Run button, the Turtles wander at random.
  2. Read the code and make sure you understand how it works. The rank003 class inherits from rank00, which means that the rank00 methods ==, ==, == and == work on Wobblers. The rank method gets invoked by TurtleWorld. It invokes rank0, which turns the Turtle in the desired direction, rank00, which makes a random turn in proportion to the Turtle's clumsiness, and rank, which moves forward a few pixels, depending on the Turtle's speed.
  3. Create a file named rank00334. Import everything from rank003, then define a class named rank00 that inherits from rank003. Call rank00338 passing the rank00 class object as an argument.
  4. Add a rank0 method to rank00 to override the one in rank003. As a starting place, write a version that always points the Turtle toward the origin. Hint: use the math function rank0 and the Turtle attributes *, * and rank003.
  5. Modify rank0 so that the Turtles stay in bounds. For debugging, you might want to use the Step button, which invokes rank once on each Turtle.
  6. Modify rank0 so that each Turtle points toward its nearest neighbor. Hint: Turtles have an attribute, rank0, that is a reference to the TurtleWorld they live in, and the TurtleWorld has an attribute, rank003, that is a list of all Turtles in the world.
  7. Modify rank0 so the Turtles play tag. You can add methods to rank00 and you can override rank0 and rank0035, but you may not modify or override rank, rank00 or rank. Also, rank0 is allowed to change the heading of the Turtle but not the position. Adjust the rules and your rank0 method for good quality play; for example, it should be possible for the slow Turtle to tag the faster Turtles eventually.
  1. 下载 rank00361 并运行。你会看到一个有三个 Turtle 的 TurtleWorld。按下 Run 按钮,Turtle 会随机游走。
  2. 阅读代码,确保你理解它的工作原理。rank003 类继承自 rank00,这意味着 rank00 的 ======== 方法在 Wobbler 上同样可用。 rank 方法由 TurtleWorld 调用。它会调用 rank0(让 Turtle 转向期望方向)、rank00(根据 Turtle 的笨拙程度做一个随机转向),以及 rank(根据 Turtle 的速度前进几个像素)。
  3. 创建一个名为 rank00373 的文件。从 rank003 导入所有内容,然后定义一个继承自 rank003 的类 rank00。调用 rank00377,把 rank00 类对象作为参数传入。
  4. rank00 添加一个 rank0 方法,覆盖 rank003 里的那个。作为起点,先写一个总是让 Turtle 朝向原点的版本。提示:用数学函数 rank0 以及 Turtle 的属性 **rank003。
  5. 修改 rank0,让 Turtle 保持在边界内。调试时你可以用 Step 按钮,它会让每个 Turtle 调用一次 rank
  6. 修改 rank0,让每个 Turtle 朝向离它最近的邻居。提示:Turtle 有一个属性 rank0,指向它所在的 TurtleWorld;而 TurtleWorld 有一个属性 rank003,是世界里所有 Turtle 的列表。
  7. 修改 rank0,让 Turtle 玩抓人游戏。你可以给 rank00 添加方法,也可以重写 rank0 和 rank0039,但不得修改或重写 rankrank00 或 rank。此外,rank0 可以改 Turtle 的朝向,但不能改它的位置。 调整规则和你的 rank0 方法,让游戏玩起来有质量;例如,慢的 Turtle 最终应当能抓到快的 Turtle。

Solution: rank00400.

答案:rank00401。