python风格的纸牌

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()

def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]

def __len__(self):
return len(self._cards)

def __getitem__(self, position):
return self._cards[position]

这个 FrenchDeck 类是一个模拟法国纸牌牌组的类。它包含了纸牌牌组的基本属性和功能。下面是对这个类的详细解释:
类变量:
ranks:这是一个列表,包含了一副牌中所有的等级(Rank),即从 2 到 10 的数字和四个面牌(J, Q, K, A)。
suits:这是一个列表,包含了一副牌中所有的花色(Suit),即 spades(黑桃)、diamonds(方块)、clubs(梅花)和 hearts(红心)。
初始化方法 init
在这个方法中,创建了一个名为 _cards 的私有属性,它是一个列表,包含了所有可能的牌面组合。这是通过一个列表推导式完成的,对于 suits 列表中的每一个花色和 ranks 列表中的每一个等级,创建一个 Card 对象。
魔术方法 len
这个方法使得 FrenchDeck 实例可以使用内置的 len() 函数来获取牌组中牌的数量。它返回 _cards 列表的长度,即牌组中牌的总数。
魔术方法 getitem
这个方法允许通过索引访问 FrenchDeck 实例中的元素,就像访问列表一样。这意味着你可以使用下标操作(例如 deck[0])来获取牌组中的特定牌。这也使得 FrenchDeck 实例支持迭代和切片操作,因为这些操作都依赖于 getitem 方法。

1
2
3
4
5
beer_card = Card('7', 'diamonds')
beer_card
'''
output:Card(rank='7', suit='diamonds')
'''
1
2
3
4
5
deck = FrenchDeck()
len(deck)
'''
output:52
'''
1
2
3
4
deck[0]
'''
output:Card(rank='2', suit='spades')
'''

使用random.choice随机选取一张牌。

1
2
3
4
5
6
from random import choice

choice(deck)
'''
output:Card(rank='5', suit='clubs')
'''

可以看出,通过特殊方法利用python数据模型,有如下两个优点

  1. 类的用户不需要记住标准操作的方法名称(“怎样获取项数?使用.size(),.length()还是其他方法”)
  2. 可以充分利用python标准库,无需重新发明轮子
    抽取最上面三张及从索引12开始,跳过13张牌,只抽取4张A。
1
2
3
4
5
6
deck[:3]
'''
output:[Card(rank='2', suit='spades'),
Card(rank='3', suit='spades'),
Card(rank='4', suit='spades')]
'''
1
2
3
4
5
6
7
deck[12::13]
'''
output:[Card(rank='A', suit='spades'),
Card(rank='A', suit='diamonds'),
Card(rank='A', suit='clubs'),
Card(rank='A', suit='hearts')]
'''

纸牌还可以迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
for card in deck:
print(card)

'''
output:Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
Card(rank='5', suit='spades')
Card(rank='6', suit='spades')
Card(rank='7', suit='spades')
Card(rank='8', suit='spades')
Card(rank='9', suit='spades')
Card(rank='10', suit='spades')
Card(rank='J', suit='spades')
Card(rank='Q', suit='spades')
Card(rank='K', suit='spades')
Card(rank='A', suit='spades')
Card(rank='2', suit='diamonds')
Card(rank='3', suit='diamonds')
Card(rank='4', suit='diamonds')
Card(rank='5', suit='diamonds')
Card(rank='6', suit='diamonds')
Card(rank='7', suit='diamonds')
Card(rank='8', suit='diamonds')
Card(rank='9', suit='diamonds')
Card(rank='10', suit='diamonds')
Card(rank='J', suit='diamonds')
Card(rank='Q', suit='diamonds')
Card(rank='K', suit='diamonds')
...
Card(rank='J', suit='hearts')
Card(rank='Q', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='A', suit='hearts')
'''

再进行排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)

def spades_high(card):
rank_value = FrenchDeck.ranks.index(card.rank)
return rank_value * len(suit_values) + suit_values[card.suit]

for card in sorted(deck, key=spades_high):
print(card)
'''
output:Card(rank='2', suit='clubs')
Card(rank='2', suit='diamonds')
Card(rank='2', suit='hearts')
Card(rank='2', suit='spades')
Card(rank='3', suit='clubs')
Card(rank='3', suit='diamonds')
Card(rank='3', suit='hearts')
Card(rank='3', suit='spades')
Card(rank='4', suit='clubs')
Card(rank='4', suit='diamonds')
Card(rank='4', suit='hearts')
Card(rank='4', suit='spades')
Card(rank='5', suit='clubs')
Card(rank='5', suit='diamonds')
Card(rank='5', suit='hearts')
Card(rank='5', suit='spades')
Card(rank='6', suit='clubs')
Card(rank='6', suit='diamonds')
Card(rank='6', suit='hearts')
Card(rank='6', suit='spades')
Card(rank='7', suit='clubs')
Card(rank='7', suit='diamonds')
Card(rank='7', suit='hearts')
Card(rank='7', suit='spades')
Card(rank='8', suit='clubs')
...
Card(rank='A', suit='clubs')
Card(rank='A', suit='diamonds')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')
'''

特殊方法是如何使用的

首先明确一点,特殊方法是供python解释器使用的,而不是你自己。也就是说,没有my_object.__len__()这种写法,正确的写法是len(my_object)。

1.3.1 模拟数值类型

例二:实现一个二维向量类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import math

class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y

def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)

def __abs__(self):
return math.hypot(self.x, self.y)

def __bool__(self):
return bool(abs(self))

def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x, y)

def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)

向量加法

1
2
3
4
5
6
v1 = Vector(2, 4)
v2 = Vector(2, 1)
v1 + v2
'''
output:Vector(4, 5)
'''

计算向量的模

1
2
3
4
5
v = Vector(3, 4)
abs(v)
'''
output:5.0
'''

实现"*"运算符,计算向量的标量积。

1
2
3
4
5
v = v * 3
v
'''
output:Vector(9, 12)
'''

1.3.2 字符串表示形式

特殊方法__repr__供内置函数repr调用,获取对象的字符串表示形式。

与此形成对照的是,__str__方法由内置函数str()调用,在背后供print函数使用。

1.3.3 自定义类型的布尔值

默认情况下,用户定义的实例都是真值,除非实现了__bool___或__len__方法。简单来说,bool(x)调用了x.__bool__方法,以后者得到的结果为准。如果没有实现__bool__方法,则python尝试调用x.__len__方法。

在本例中,如果向量的模为零,则返回False,否则返回True。

1.3.4 容器API

基本容器类型
顶部三个抽象基类均只有一个特殊方法。抽象基类Collection统一了这三个基本接口,每一个容器类型均应实现如下事项:

  • Iterable要支持for、拆包和其他迭代方式;
  • Sized要支持内置函数len;
  • Container要支持in运算符。
    Collection还有三个重要的专用接口:
  • Sequence规范list和str等内置类型的接口;
  • Mapping被dict、collections.defaultdict等实现;
  • Set是set和frozenset两个内置类型的接口。
    只有Sequence实现了Reversible,因为序列要支持以任意顺序排列内容。