python 魔术方法2 序列

2019-06-21  本文已影响0人  Vector_Wan

因为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值。

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

下面我们一起来看一个序列的小例子:
模拟生成一个扑克牌序列:
在这里呢我们使用了命名元组 '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))

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

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

上一篇下一篇

猜你喜欢

热点阅读