python 中random.shuffle 函数打乱列表中的元素 和 猴子补丁

标准库中的 random.shuffle 函数用法如下:
![在这里插入描述]失https://败(imblog.csdnig-mg.cn/20190819232644152.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01aUF9tYW4=,size_16,color_FFFFFF,t_70)这https://im,g-blog.csdnimg.cn/20190819232644152.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01aUF9tYW4=,size_16,color_FFFFFF,t_70)]上
上面是对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 函数不关 心参数的类型,只要那个对象实现了部分可变序列协议即可。即便对象一开始没有所需的方法也没关系,后来再提供也行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值