Python 教程 18 — 继承-Python 实用宝典

# Python 教程 18 — 继承

```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

import random

class Card:
"""Represents a standard playing card.

Attributes:
suit: integer 0-3
rank: integer 1-13
"""

suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "Jack", "Queen", "King"]

def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank

def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],
Card.suit_names[self.suit])

def __eq__(self, other):
"""Checks whether self and other have the same rank and suit.
returns: boolean
"""
return self.suit == other.suit and self.rank == other.rank

def __lt__(self, other):
"""Compares this card to other, first by suit, then rank.
returns: boolean
"""
t1 = self.suit, self.rank
t2 = other.suit, other.rank
return t1 < t2

class Deck:
"""Represents a deck of cards.
Attributes:
cards: list of Card objects.
"""

def __init__(self):
"""Initializes the Deck with 52 cards.
"""
self.cards = []
for suit in range(4):
for rank in range(1, 14):
card = Card(suit, rank)
self.cards.append(card)

def __str__(self):
"""Returns a string representation of the deck.
"""
res = []
for card in self.cards:
res.append(str(card))
return '\n'.join(res)

"""Adds a card to the deck.
card: Card
"""
self.cards.append(card)

def remove_card(self, card):
"""Removes a card from the deck or raises exception if it is not there.

card: Card
"""
self.cards.remove(card)

def pop_card(self, i=-1):
"""Removes and returns a card from the deck.
i: index of the card to pop; by default, pops the last card.
"""
return self.cards.pop(i)

def shuffle(self):
"""Shuffles the cards in this deck."""
random.shuffle(self.cards)

def sort(self):
"""Sorts the cards in ascending order."""
self.cards.sort()

def move_cards(self, hand, num):
"""Moves the given number of cards from the deck into the Hand.
hand: destination Hand object
num: integer number of cards to move
"""
for i in range(num):

class Hand(Deck):
"""Represents a hand of playing cards."""

def __init__(self, label=''):
self.cards = []
self.label = label

def find_defining_class(obj, method_name):
"""Finds and returns the class object that will provide
the definition of method_name (as a string) if it is
invoked on obj.
obj: any python object
method_name: string method name
"""
for ty in type(obj).mro():
if method_name in ty.__dict__:
return ty
return None

if __name__ == '__main__':
deck = Deck()
deck.shuffle()

hand = Hand()
print(find_defining_class(hand, 'shuffle'))

deck.move_cards(hand, 5)
hand.sort()
print(hand)```

## 1.卡牌对象 #

`Card`的类定义如下：

```class Card:
"""代表一张标准的卡牌"""

def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank```

`queen_of_diamonds = Card(1, 12)`

## 2.类属性 #

```# 在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])```

`rank_names`的第一个元素是 `None` ，因为没有卡牌的等级是 0 。 通过使用 `None` 作为占位符，我们可以很好地将索引 2 映射到字符串`'2'`，等等。 为了避免使用这种小技巧，我们也可以使用一个字典来代替列表。

```>>> card1 = Card(2, 11)
>>> print(card1)
Jack of Hearts```

## 3.比较卡牌 #

`__lt__`接受 2 个参数, `self` 和 `other`，如果 `self` 比 `other` 的值要小则返回 `True` 。

```# 在Card类内部:

def __lt__(self, other):
# 判断花色
if self.suit < other.suit: return True
if self.suit > other.suit: return False

# 花色相同...判断等级
return self.rank < other.rank```

```# 在Card类内部:

def __lt__(self, other):
t1 = self.suit, self.rank
t2 = other.suit, other.rank
return t1 < t2```

## 4.一副牌 #

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

## 5.打印一副牌 #

```# Deck类的内部

def __str__(self):
res = []
for card in self.cards:
res.append(str(card))
return '\n'.join(res)```

```>>> deck = Deck()
>>> print(deck)
Ace of Clubs
2 of Clubs
3 of Clubs
...

## 6.添加，移除，洗牌和排序 #

```# 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.

```# Deck类的内部

self.cards.append(card)```

```# Deck类的内部

def shuffle(self):
random.shuffle(self.cards)```

## 7.继承 #

```class Hand(Deck):
"""Represents a hand of playing cards."""```

```# Hand 类的内部

def __init__(self, label=''):
self.cards = []
self.label = label```

```>>> hand = Hand('new hand')
>>> hand.cards
[]
>>> hand.label
'new hand'```

```>>> deck = Deck()
>>> card = deck.pop_card()
>>> print(hand)

```# Deck类的内部

def move_cards(self, hand, num):
for i in range(num):

`move_cards`接受两个参数，一个是 `Hand` 对象，另一个是发牌的数量。 它会同时修改 `self` 和 `hand` ，然后返回 `None` 。

## 8.类图 #

• 一个类中的对象可以包含对另外一个类的对象的引用。例如，每一个矩形包含对点的引用，每一个 `Deck` 包含对许多 `Card` 的引用。这种关系被称为组合( HAS-A )，可以类似这样描述：“一个矩形有一个（has a）点”。
• 一个类可能继承自另外一个类。这种关系被称为继承(IS-A)，可以类似这样描述：“Hand is a kind of Deck”。
• 一个类可能强赖另一个类，因为前者中的对象接受后者中的对象作为参数，或者使用后者中的对象作为计算的一部分。这种关系被称为 依赖 。

## 9.数据封装 #

```suffix_map = {}
prefix = ()```

```class Markov:

def __init__(self):
self.suffix_map = {}
self.prefix = ()```

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

1. 首先编写读取全局变量的函数（如有必要）。
2. 一旦你让程序跑起来了，开始查找全局变量和使用它们的函数的联系。
3. 封装相关的变量作为一个对象的属性。
4. 转换相关函数为新类的方法。

```"""This module contains a code example related to

Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com

"""

from __future__ import print_function, division

import sys
import string
import random

# global variables
suffix_map = {}        # map from prefixes to a list of suffixes
prefix = ()            # current tuple of words

def process_file(filename, order=2):
"""Reads a file and performs Markov analysis.

filename: string
order: integer number of words in the prefix

returns: map from prefix to list of possible suffixes.
"""
fp = open(filename)

for line in fp:
if line.startswith('*** END OF THIS'):
break

for word in line.rstrip().split():
process_word(word, order)

"""Reads from fp until it finds the line that ends the header.

fp: open file object
"""
for line in fp:
if line.startswith('*** START OF THIS'):
break

def process_word(word, order=2):
"""Processes each word.

word: string
order: integer

During the first few iterations, all we do is store up the words;
after that we start adding entries to the dictionary.
"""
global prefix
if len(prefix) < order:
prefix += (word,)
return

try:
suffix_map[prefix].append(word)
except KeyError:
# if there is no entry for this prefix, make one
suffix_map[prefix] = [word]

prefix = shift(prefix, word)

def random_text(n=100):
"""Generates random wordsfrom the analyzed text.

Starts with a random prefix from the dictionary.

n: number of words to generate
"""
# choose a random prefix (not weighted by frequency)
start = random.choice(list(suffix_map.keys()))

for i in range(n):
suffixes = suffix_map.get(start, None)
if suffixes == None:
# if the start isn't in map, we got to the end of the
# original text, so we have to start again.
random_text(n-i)
return

# choose a random suffix
word = random.choice(suffixes)
print(word, end=' ')
start = shift(start, word)

def shift(t, word):
"""Forms a new tuple by removing the head and adding word to the tail.

t: tuple of strings
word: string

Returns: tuple of strings
"""
return t[1:] + (word,)

def main(script, filename='158-0.txt', n=100, order=2):
try:
n = int(n)
order = int(order)
except ValueError:
print('Usage: %d filename [# of words] [prefix length]' % script)
else:
process_file(filename, order)
random_text(n)
print()

if __name__ == '__main__':
main(*sys.argv)```

## 10.调试 #

```def find_defining_class(obj, meth_name):
for ty in type(obj).mro():
if meth_name in ty.__dict__:
return ty```

```>>> hand = Hand()
>>> find_defining_class(hand, 'shuffle')
<class 'Card.Deck'>```

`find_defining_class`使用 `mro` 方法获得将类对象（类型）的列表， 解释器将会从这里依次搜索哪个类提供了这个方法。“MOR”是“method resolution order”的简称，指的是Python “解析” 方法名时将搜索的一个类序列。

## 11.术语表 #

IS-A 关系：子类和父类之间的关系。

HAS-A 关系：两个类之中，有一个类包含对另一个类的实例的引用的关系。

## 12.练习题 #

### 习题18-1 #

```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

self.pings.append(ping)

pong = Pong()
ping = Ping(pong)

### 习题18-3 #

1. 从下方折叠组件中获取以下文件：Card.py: 本章中完整版本的Card , Deck和Hand类。PokerHand.py: 代表 poker hand 的不完整的实现，和一些测试代码。
2. 如果你运行 `PokerHand.py` ,它会发放 7 张牌的 poker hand，检查是否含有顺子。仔细阅读代码，再继续下面的内容。
3. 往 `PokerHand.py` 文件中添加叫做 `has_pair` 、 `has_twopair` 等方法，这些方法根据手牌是否满足相应的标准来返回 `True` 或 `False` 。你的代码应该可以正确地处理包含任意卡牌数量（虽然 5 和 7 是最常见的数量）的手牌。
4. 写一个叫 `classify` 的方法，计算出一个手牌的最高值分类，然后设置对应的 `label` 属性。例如，一个 7 张牌的手牌可能包含一个顺子和一个对子；那么它应该标注为“顺子”。
5. 确信你的分类方法是正确的之后，下一步是估算这些不同手牌出现的几率。在 `PokerHand.py` 中编写一个函数，完成洗牌，分牌，对牌分类，然后记录每种分类出现的次数。
6. 打印每种分类和对应频率的表格。运行你的程序，不断增加手牌的卡牌数量，直到输出的值保持在足够准确的范围。将你的结果和http://en.wikipedia.org/wiki/Hand_rankings 页面中的的值进行比较。
```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

import random

class Card:
"""Represents a standard playing card.

Attributes:
suit: integer 0-3
rank: integer 1-13
"""

suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "Jack", "Queen", "King"]

def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank

def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],
Card.suit_names[self.suit])

def __eq__(self, other):
"""Checks whether self and other have the same rank and suit.
returns: boolean
"""
return self.suit == other.suit and self.rank == other.rank

def __lt__(self, other):
"""Compares this card to other, first by suit, then rank.
returns: boolean
"""
t1 = self.suit, self.rank
t2 = other.suit, other.rank
return t1 < t2

class Deck:
"""Represents a deck of cards.
Attributes:
cards: list of Card objects.
"""

def __init__(self):
"""Initializes the Deck with 52 cards.
"""
self.cards = []
for suit in range(4):
for rank in range(1, 14):
card = Card(suit, rank)
self.cards.append(card)

def __str__(self):
"""Returns a string representation of the deck.
"""
res = []
for card in self.cards:
res.append(str(card))
return '\n'.join(res)

"""Adds a card to the deck.
card: Card
"""
self.cards.append(card)

def remove_card(self, card):
"""Removes a card from the deck or raises exception if it is not there.

card: Card
"""
self.cards.remove(card)

def pop_card(self, i=-1):
"""Removes and returns a card from the deck.
i: index of the card to pop; by default, pops the last card.
"""
return self.cards.pop(i)

def shuffle(self):
"""Shuffles the cards in this deck."""
random.shuffle(self.cards)

def sort(self):
"""Sorts the cards in ascending order."""
self.cards.sort()

def move_cards(self, hand, num):
"""Moves the given number of cards from the deck into the Hand.
hand: destination Hand object
num: integer number of cards to move
"""
for i in range(num):

class Hand(Deck):
"""Represents a hand of playing cards."""

def __init__(self, label=''):
self.cards = []
self.label = label

def find_defining_class(obj, method_name):
"""Finds and returns the class object that will provide
the definition of method_name (as a string) if it is
invoked on obj.
obj: any python object
method_name: string method name
"""
for ty in type(obj).mro():
if method_name in ty.__dict__:
return ty
return None

if __name__ == '__main__':
deck = Deck()
deck.shuffle()

hand = Hand()
print(find_defining_class(hand, 'shuffle'))

deck.move_cards(hand, 5)
hand.sort()
print(hand)```
```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

from Card import Hand, Deck

class PokerHand(Hand):
"""Represents a poker hand."""

def suit_hist(self):
"""Builds a histogram of the suits that appear in the hand.
Stores the result in attribute suits.
"""
self.suits = {}
for card in self.cards:
self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

def has_flush(self):
"""Returns True if the hand has a flush, False otherwise.

Note that this works correctly for hands with more than 5 cards.
"""
self.suit_hist()
for val in self.suits.values():
if val >= 5:
return True
return False

if __name__ == '__main__':
# make a deck
deck = Deck()
deck.shuffle()

# deal the cards and classify the hands
for i in range(7):
hand = PokerHand()
deck.move_cards(hand, 7)
hand.sort()
print(hand)
print(hand.has_flush())
print('')```
```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

from Card import Hand, Deck

class Hist(dict):
"""A map from each item (x) to its frequency."""

def __init__(self, seq=[]):
"Creates a new histogram starting with the items in seq."
for x in seq:
self.count(x)

def count(self, x, f=1):
"Increments (or decrements) the counter associated with item x."
self[x] = self.get(x, 0) + f
if self[x] == 0:
del self[x]

class PokerHand(Hand):
"""Represents a poker hand."""

all_labels = ['straightflush', 'fourkind', 'fullhouse', 'flush',
'straight', 'threekind', 'twopair', 'pair', 'highcard']

def make_histograms(self):
"""Computes histograms for suits and hands.
Creates attributes:
suits: a histogram of the suits in the hand.
ranks: a histogram of the ranks.
sets: a sorted list of the rank sets in the hand.
"""
self.suits = Hist()
self.ranks = Hist()

for c in self.cards:
self.suits.count(c.suit)
self.ranks.count(c.rank)

self.sets = list(self.ranks.values())
self.sets.sort(reverse=True)

def has_highcard(self):
"""Returns True if this hand has a high card."""
return len(self.cards)

def check_sets(self, *t):
"""Checks whether self.sets contains sets that are
at least as big as the requirements in t.
t: list of int
"""
for need, have in zip(t, self.sets):
if need > have:
return False
return True

def has_pair(self):
"""Checks whether this hand has a pair."""
return self.check_sets(2)

def has_twopair(self):
"""Checks whether this hand has two pair."""
return self.check_sets(2, 2)

def has_threekind(self):
"""Checks whether this hand has three of a kind."""
return self.check_sets(3)

def has_fourkind(self):
"""Checks whether this hand has four of a kind."""
return self.check_sets(4)

def has_fullhouse(self):
"""Checks whether this hand has a full house."""
return self.check_sets(3, 2)

def has_flush(self):
"""Checks whether this hand has a flush."""
for val in self.suits.values():
if val >= 5:
return True
return False

def has_straight(self):
"""Checks whether this hand has a straight."""
# make a copy of the rank histogram before we mess with it
ranks = self.ranks.copy()
ranks = ranks.get(1, 0)

# see if we have 5 in a row
return self.in_a_row(ranks, 5)

def in_a_row(self, ranks, n=5):
"""Checks whether the histogram has n ranks in a row.
hist: map from rank to frequency
n: number we need to get to
"""
count = 0
for i in range(1, 15):
if ranks.get(i, 0):
count += 1
if count == n:
return True
else:
count = 0
return False

def has_straightflush(self):
"""Checks whether this hand has a straight flush.
Clumsy algorithm.
"""
# make a set of the (rank, suit) pairs we have
s = set()
for c in self.cards:
if c.rank == 1:

# iterate through the suits and ranks and see if we
# get to 5 in a row
for suit in range(4):
count = 0
for rank in range(1, 15):
if (rank, suit) in s:
count += 1
if count == 5:
return True
else:
count = 0
return False

def has_straightflush(self):
"""Checks whether this hand has a straight flush.
Better algorithm (in the sense of being more demonstrably
correct).
"""
# partition the hand by suit and check each
# sub-hand for a straight
d = {}
for c in self.cards:

# see if any of the partitioned hands has a straight
for hand in d.values():
if len(hand.cards) < 5:
continue
hand.make_histograms()
if hand.has_straight():
return True
return False

def classify(self):
"""Classifies this hand.
Creates attributes:
labels:
"""
self.make_histograms()

self.labels = []
for label in PokerHand.all_labels:
f = getattr(self, 'has_' + label)
if f():
self.labels.append(label)

class PokerDeck(Deck):
"""Represents a deck of cards that can deal poker hands."""

def deal_hands(self, num_cards=5, num_hands=10):
"""Deals hands from the deck and returns Hands.
num_cards: cards per hand
num_hands: number of hands
returns: list of Hands
"""
hands = []
for i in range(num_hands):
hand = PokerHand()
self.move_cards(hand, num_cards)
hand.classify()
hands.append(hand)
return hands

def main():
# the label histogram: map from label to number of occurances
lhist = Hist()

# loop n times, dealing 7 hands per iteration, 7 cards each
n = 10000
for i in range(n):
if i % 1000 == 0:
print(i)

deck = PokerDeck()
deck.shuffle()

hands = deck.deal_hands(7, 7)
for hand in hands:
for label in hand.labels:
lhist.count(label)

# print the results
total = 7.0 * n
print(total, 'hands dealt:')

for label in PokerHand.all_labels:
freq = lhist.get(label, 0)
if freq == 0:
continue
p = total / freq
print('%s happens one time in %.2f' % (label, p))

if __name__ == '__main__':
main()```

1. 翻译：@bingjin
2. 校对：@bingjin
3. 参考：@carfly

## 推荐阅读#

##### Wolfram: ChatGPT是怎么实现的？为什么它这么有效？#
ChatGPT 能够自动生成类似于人类写作的文本，这一点非常引人注目，也令人意外。但它是如何实现的？为什么它能…
##### Python 比较两个时间序列在图形上是否相似#

​Python实用宝典 (pythondict.com)