Python中序列的修改、散列与切片详解
前言
本文主要给大家介绍了关于Python中序列的修改、散列与切片的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。
Vector类:用户定义的序列类型
我们将使用组合模式实现Vector类,而不使用继承。向量的分量存储在浮点数数组中,而且还将实现不可变扁平序列所需的方法。
Vector类的第1版要尽量与前一章定义的Vector2d类兼容。
Vector类第1版:与Vector2d类兼容
Vector类的第1版要尽量与前一章定义的Vector2d类兼容。然而我们会故意不让Vector的构造方法与Vector2d的构造方法兼容。为了编写Vector(3,4)和Vector(3,4,5)这样的代码,我们可以让__init__方法接受任意个参数(通过*args);但是,序列类型的构造方法最好接受可迭代的对象为参数,因为所有内置的序列类型都是这样做的。
测试Vector.__init__和Vector.__repr__方法
>>>Vector([3.1,4.2]) Vector([3.1,4.2]) >>>Vector((3,4,5)) Vector([3.0,4.0,5.0]) >>>Vector(range(10)) Vector([0.0,1.0,2.0,3.0,4.0,...])
vector_v1.py:从vector2d_v1.py衍生而来
fromarrayimportarray importreprlib importmath classVector: typecode='d' def__init__(self,components): self._components=array(self.typecode,components)#self._components是“受保护的”实例属性,把Vector的分量保存在一个数组中 def__iter__(self): returniter(self._components)#为了迭代,我们使用self._components构建一个迭代器 def__repr__(self): components=reprlib.repr(self._components)#使用reprlib.repr()函数获取self._components的有限长度表示形式(如array('d',[0.0,1.0,2.0,3.0,4.0,...])) components=components[components.find('['):-1]#把字符串插入Vector的构造方法调用之前,去掉前面的array('d'和后面的) return'Vecotr({})'.format(components)#直接使用self._components构建bytes对象 def__str__(self): returnstr(tuple(self)) def__bytes__(self): return(bytes([ord(self.typecode)])+ bytes(self._components)) def__eq__(self,other): returntuple(self)==tuple(other) def__abs__(self): returnmath.hypot(sum(x*xforxinself))#不能使用hypot方法了,因此我们先计算各分量的平方之和,然后再使用sqrt方法开平方 def__bool__(self): returnbool(abs(self)) @classmethod deffrombytes(cls,octets): typedcode=chr(octets[0]) memv=memoryview(octets[1:]).cast(typedcode) returncls(memv)#我们只需在Vector2d.frombytes方法的基础上改动最后一行:直接把memoryview传给构造方法,不用像前面那样使用*拆包
协议和鸭子类型
在Python中创建功能完善的序列类型无需使用继承,只需实现符合序列协议的方法。不过,这里说的协议是什么呢?
在面向对象编程中,协议是非正式的接口,只在文档中定义,在代码中不定义。例如,Python的序列协议只需要__len__和__getitem__两个方法。任何类(如Spam),只要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。Spam是不是哪个类的子类无关紧要,只要提供了所需的方法即可。
classFrenchDeck: ranks=[str(n)forninrange(2,11)]+list('JQKA') suits='spadesdiamondsclubshearts'.split() def__init__(self): self._cards=[Card(rank,suit)forsuitinself.suitsforrankinself.ranks] def__len__(self): returnlen(self._cards) def__getitem__(self,position): returnself._cards[position]
协议是非正式的,没有强制力,因此如果你知道类的具体使用场景,通常只需要实现一个协议的部分。例如,为了支持迭代,只需实现__getitem__方法,没必要提供__len__方法。
Vector类第2版:可切片的序列
如FrenchDeck类所示,如果能委托给对象中的序列属性(如self._components数组),支持序列协议特别简单。下述只有一行代码的__len__和__getitem__方法是个好的开始:
classVector: #省略了很多行 #... def__len__(self): returnlen(self._components) def__getitem__(self,index): returnself._components[index]
添加这两个方法之后,就能执行下述操作了:
>>>v1=Vector([3,4,5]) >>>len(v1) >>>v1[0],v1[-1] (3.0,5.0) >>>v7=Vector(range(7)) >>>v7[1:4] array('d',[1.0,2.0,3.0])
可以看到,现在连切片都支持了,不过尚不完美。如果Vector实例的切片也是Vector实例,而不是数组,那就更好了。前面那个FrenchDeck类也有类似的问题:切片得到的是列表。对Vector来说,如果切片生成普通的数组,将会缺失大量功能。
为了把Vector实例的切片也变成Vector实例,我们不能简单地委托给数组切片。我们要分析传给__getitem__方法的参数,做适当的处理。
切片原理
了解__getitem__和切片的行为
>>>classMySeq: ...def__getitem__(self,index): ...returnindex ... >>>s=MySeq() >>>s[1]#__getitem__直接返回传给它的值 >>>s[1:4]#[1:4]表示变成了slice(1,4,None) slice(1,4,None) >>>s[1:4:2]#[1:4:2]的意思为从第1个索引开始,到第4个索引结束,步长为2 slice(1,4,2) >>>s[1:4:2,9] (slice(1,4,2),9)#神奇的事情发生了..wtf...如果[]中有逗号,那么__getitem__接收的是元祖 >>>s[1:4:2,7:9]#元祖中还可以包含多个切片对象 (slice(1,4,2),slice(7,9,None))