标准库中的 random.shuffle 函数用法如下:
上面是对python内置的list 进行打乱的做法,下面我们来说明用户自定义的类如何实现打乱对象的做法呢,其实很简单只要自定义的类满足对应(列表)的接口的协议就可以啦!
import collections
from random import shuffle
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]
if __name__ == "__main__":
deck = FrenchDeck()
print(shuffle(deck))
运行结果:
这个问题的原因是,shuffle 函数要调换集合中元素的位置,而 FrenchDeck 只实现了不可变的序列协议。可变的序列还必须提供 __setitem__
方法。
下面代码中添加了__setitem__
方法。
import collections
from random import shuffle
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]
def __setitem__(self, key, value):
self._cards[key] = value
if __name__ == "__main__":
deck = FrenchDeck()
shuffle(deck)
for i in deck:
print(i)
运行结果:
这里就可以实现对自定义的类顺序进行打乱的做法。
当然python是动态语言,因此可以在运行的过程中进行中添加代码,比如我们先定义一个函数:
def set_card(deck, position, card):
deck._cards[position] = card
然后将函数赋值给 FrenchDeck
类的 __setitem__
属性。
FrenchDeck.__setitem__ = set_card
这样也能实现了,语言 参考中使用的参数是 self、key 和 value,而这里使用的是 deck、position 和 card。这么 做是为了告诉你,每个 Python 方法说到底都是普通函数,把第一个参数命名为 self 只是 一种约定。在控制台会话中使用那几个参数没问题,不过在 Python 源码文件中最好按照文 档那样使用 self、key 和 value。
这里的关键是,set_card 函数要知道 deck 对象有一个名为 _cards 的属性,而且 _cards 的 值必须是可变序列。然后,我们把 set_card 函数赋值给特殊方法 __setitem__
,从而把它 依附到 FrenchDeck
类上。这种技术叫猴子补丁:在运行时修改类或模块,而不改动源码。 猴子补丁很强大,但是打补丁的代码与要打补丁的程序耦合十分紧密,而且往往要处理隐 藏和没有文档的部分。
除了举例说明猴子补丁之外,示例还强调了协议是动态的:random.shuffle
函数不关 心参数的类型,只要那个对象实现了部分可变序列协议即可。即便对象一开始没有所需的方法也没关系,后来再提供也行。