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数据模型,有如下两个优点
- 类的用户不需要记住标准操作的方法名称(“怎样获取项数?使用.size(),.length()还是其他方法”)
- 可以充分利用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,因为序列要支持以任意顺序排列内容。