因为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()
函数, 它是实现切片的对象,主要用在切片操作函数里的参数传递。
网友评论