美文网首页
python 魔术方法2 序列

python 魔术方法2 序列

作者: Vector_Wan | 来源:发表于2019-06-21 21:15 被阅读0次

    因为python是一个动态语言,它使得继承并不是必须的,它在创建功能完善的序列类型无需使用继承,只需要实现符合序列协议的方法。协议是非正式的接口,也就是说只是在文档中定义,如果你不按照协议来走,解释器也会正常运行。

    有一句著名的名言:When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck. - James Whitcomb Riley
    我们不关注对象的类型,而关注对象的行为(方法)。它的行为是鸭子的行为,那么可以认为它是鸭子。

    python 的序列协议只需要实现 __len____getitem__ 两个特殊方法即可,任何类,只要使用标准的签名和语义实现了这两个方法,就可以用在任何期待序列的地方。神奇吧。

    __len__很容易想到,在调用len方法的时候被调用,而 __getitem__是这样被调用的:
    如果在类中定义了__getitem__()方法,那么他的实例对象(假设为P)就可以这样P[key]取值。当实例对象做P[key]运算时,就会调用类中的__getitem__()方法。

    class DataTest:
        def __init__(self):
            pass
            
        def __getitem__(self,key):
            return "hello"
        
     
    data = DataTest()
    print (data[2]) # hello
    

    在这我认为实例对象的key不管是否存在都会调用类中的__getitem__()方法。而且返回值就是__getitem__()方法中规定的return值。

    在这里在补充几个关于序列的魔术方法:

    • __getitem__ (self, item): 需要返回键item对应的值;
    • __setitem__ (self,key,value): 需要设置给定键key的值 value;
    • __delitem__ (self,key): 删除给定键对应的元素。

    下面我们一起来看一个序列的小例子:
    模拟生成一个扑克牌序列:
    在这里呢我们使用了命名元组 'Card'来表示每一个扑克牌,每一张扑克牌是由 它的值 与花色组成。这挺起来还是很符合逻辑的,那么我们相把它实现成一个序列,在后面我们就可以使用任意一个序列方法了,比如切片。我们只需要实现__len____getitem__ 就可以:

    import  collections
    
    Card = collections.namedtuple('Card', ['rank', 'suit'])
    
    # 命名元组 等价于:
    # class Card:
    #     def __init__(self, rank, suit):
    #         self.rank = rank
    #         self.suit = suit
    
    class Puke:
        ranks = [str(n) for n in range(2, 11)] + list('JQKA')
        suits = '♠ ♦ ♣ ♥'.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, item):
            return self._cards[item]
        
        #为了能够执行洗牌、赋值操作#为了能够执行洗牌、赋值操作
        def __setitem__(self, key, value):
            self._cards[key] = value
    
    
    
    if __name__ == "__main__":
        pk = Puke()
        for card in pk:
            print(card)
    

    输出的结果大概类似于这种(截取了一部分)

    Card(rank='2', suit='♠')
    Card(rank='3', suit='♠')
    Card(rank='4', suit='♠')
    Card(rank='5', suit='♠')
    Card(rank='6', suit='♠')
    Card(rank='7', suit='♠')
    Card(rank='8', suit='♠')
    Card(rank='9', suit='♠')
    Card(rank='10', suit='♠')
    Card(rank='J', suit='♠')
    Card(rank='Q', suit='♠')
    

    序列中比较常见的操作就是切片操作,

    pk = Puke()
    print(pk[2:6]) # 第三到第六张
    print(pk[12::13]) # 打印所有的 A
    
    [Card(rank='4', suit='♠'), Card(rank='5', suit='♠'), Card(rank='6', suit='♠'), Card(rank='7', suit='♠')]
    [Card(rank='A', suit='♠'), Card(rank='A', suit='♦'), Card(rank='A', suit='♣'), Card(rank='A', suit='♥')]
    

    如果我们想将前三张都修改为:rank='A', suit='♠',可以这样:

    pk[1:3] = [Card(rank='A', suit='♠')] * 3
    

    接下来是洗牌操作,我们使用了shuffle()方法,它可以将序列的所有元素随机排序。

    # 洗牌
    import random
    random.shuffle(pk)
    

    说到这里我们来仔细研究一下切片的原理,我们还是先来看一个例子:

    class MySeq:
        def __getitem__(self, item):
            return item
    
    s = MySeq()
    
    print(s[1]) # 1
    print(s[1:4]) # slice(1, 4, None)
    print(s[1:4:2]) # slice(1, 4, 2)
    print(s[1:4:2,9]) # (slice(1, 4, 2), 9)
    print(s[1:4:2,7:9]) # (slice(1, 4, 2), slice(7, 9, None))
    

    我们可以得到以下的结论:

    • 如果传入的是单个值,直接返回这个值。
    • 如果传入的是[1:4],则变成了slice(1,4,None)
    • 如果传入的[]中有逗号(,),则__getitem__得到的是元组

    这里面有一个slice()函数, 它是实现切片的对象,主要用在切片操作函数里的参数传递。

    相关文章

      网友评论

          本文标题:python 魔术方法2 序列

          本文链接:https://www.haomeiwen.com/subject/ynxpqctx.html