加入收藏 | 设为首页 | 会员中心 | 我要投稿 安卓应用网 (https://www.0791zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Python > 正文

Python中序列的修改、散列与切片详解

发布时间:2020-05-28 16:22:39 所属栏目:Python 来源:互联网
导读:前言本文主要给大家介绍了关于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,5))
Vector([3.0,4.0,5.0])
>>> Vector(range(10))
Vector([0.0,1.0,2.0,3.0,...])

vector_v1.py:从 vector2d_v1.py 衍生而来

from array import array
import reprlib
import math


class Vector:
 typecode = 'd'

 def __init__(self,components):
 self._components = array(self.typecode,components)  #self._components是“受保护的”实例属性,把Vector的分量保存在一个数组中

 def __iter__(self):
 return iter(self._components)    #为了迭代,我们使用self._components构建一个迭代器

 def __repr__(self):
 components = reprlib.repr(self._components)   #使用reprlib.repr()函数获取self._components 的有限长度表示形式(如 array('d',[0.0,...]))
 components = components[components.find('['):-1]  #把字符串插入 Vector 的构造方法调用之前,去掉前面的array('d' 和后面的 )
 return 'Vecotr({})'.format(components)   #直接使用 self._components 构建 bytes 对象

 def __str__(self):
 return str(tuple(self))

 def __bytes__(self):
 return (bytes([ord(self.typecode)]) +
  bytes(self._components))

 def __eq__(self,other):
 return tuple(self) == tuple(other)

 def __abs__(self):
 return math.hypot(sum(x * x for x in self))   #不能使用hypot方法了,因此我们先计算各分量的平方之和,然后再使用sqrt方法开平方

 def __bool__(self):
 return bool(abs(self))

 @classmethod
 def frombytes(cls,octets):
 typedcode = chr(octets[0])
 memv = memoryview(octets[1:]).cast(typedcode)
 return cls(memv)      #我们只需在 Vector2d.frombytes 方法的基础上改动最后一行:直接把memoryview传给构造方法,不用像前面那样使用*拆包

协议和鸭子类型

在 Python 中创建功能完善的序列类型无需使用继承,只需实现符合序列协议的方法。不过,这里说的协议是什么呢?

在面向对象编程中,协议是非正式的接口,只在文档中定义,在代码中不定义。例如,Python 的序列协议只需要 __len__ 和 __getitem__ 两个方法。任何类(如 Spam),只要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。Spam 是不是哪个类的子类无关紧要,只要提供了所需的方法即可。

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]

协议是非正式的,没有强制力,因此如果你知道类的具体使用场景,通常只需要实现一个协议的部分。例如,为了支持迭代,只需实现__getitem__ 方法,没必要提供 __len__ 方法。

Vector类第2版:可切片的序列

如 FrenchDeck 类所示,如果能委托给对象中的序列属性(如self._components 数组),支持序列协议特别简单。下述只有一行代码的 __len__ 和 __getitem__ 方法是个好的开始:

class Vector:
 # 省略了很多行
 # ...

 def __len__(self):
 return len(self._components)

 def __getitem__(self,index):
 return self._components[index]

添加这两个方法之后,就能执行下述操作了:

>>> v1 = Vector([3,5])
>>> len(v1)
>>> v1[0],v1[-1]
(3.0,5.0)
>>> v7 = Vector(range(7))
>>> v7[1:4]
array('d',[1.0,3.0])

可以看到,现在连切片都支持了,不过尚不完美。如果 Vector 实例的切片也是 Vector 实例,而不是数组,那就更好了。前面那个FrenchDeck 类也有类似的问题:切片得到的是列表。对 Vector 来说,如果切片生成普通的数组,将会缺失大量功能。

为了把 Vector 实例的切片也变成 Vector 实例,我们不能简单地委托给数组切片。我们要分析传给 __getitem__ 方法的参数,做适当的处理。

切片原理

了解 __getitem__ 和切片的行为

>>> class MySeq:
... def __getitem__(self,index):
...  return index
... 
>>> s = MySeq()              
>>> s[1]                      #__getitem__直接返回传给它的值
>>> s[1:4]                     #[1:4]表示变成了slice(1,None)
slice(1,None)
>>> s[1:4:2]                    #[1:4:2]的意思为从第1个索引开始,到第4个索引结束,步长为2
slice(1,2)          
>>> s[1:4:2,9]                  
(slice(1,2),9)                #神奇的事情发生了..wtf...如果[]中有逗号,那么__getitem__接收的是元祖
>>> s[1:4:2,7:9]                 #元祖中还可以包含多个切片对象
(slice(1,slice(7,9,None))

🌰 查看slice类的属性

>>> slice
<class 'slice'>
>>> dir(slice)
['__class__','__delattr__','__dir__','__doc__','__eq__','__format__','__ge__','__getattribute__','__gt__','__hash__','__init__','__init_subclass__','__le__','__lt__','__ne__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__str__','__subclasshook__','indices','start','step','stop']

调用 dir(slice) 得到的结果中有个 indices 属性,这个方法有很大的作用,但是鲜为人知。help(slice.indices) 给出的信息如下。

indices(...)

S.indices(len) -> (start,stop,stride)

给定长度为 len 的序列,计算 S 表示的扩展切片的起始(start)和结尾(stop)索引,以及步幅(stride)。超出边界的索引会被截掉,这与常规切片的处理方式一样。

 举个 🌰 假设有个长度为5的序列,例如'ABCDE':

>>> slice(None,10,2).indices(5)    #'ABCDE'[:10:2]等同于[:]
(0,5,2)
>>> slice(-3,None,None).indices(5)  #'ABCDE'[-3:]等同于[2:5:1]
(2,1)

能处理切片的__getitem__方法

vector_v2.py 的部分代码:为 vector_v1.py 中的 Vector类添加 __len__ 和__getitem__ 方法

def __len__(self):
 return len(self._components)

 def __getitem__(self,index):
 cls = type(self)      #获取实例的类(Vector)
 if isinstance(index,slice):    #判断传递进来的index是否为slice对象
  return cls(self._components[index])   #调用类的构造方法,创建一个新的Vector实例
 elif isinstance(index,numbers.Integral):   #如果传递进来的是个整数
  return self._components[index]    #返回 _components 中相应的元素
 else:       #抛出异常
  msg = '{cls.__name__} indices must be integers'
  raise TypeError(msg.format(cls=cls))

测试 🌰 改进的 Vector.__getitem__ 方法

>>> v7 = Vector(range(7))
>>> v7[-1]                     #获取单个整数索引,返回一个浮点数
6.0
>>> v7[1:4]                     #切片索引创建一个新的Vector实例
Vector([1.0,3.0])
>>> v7[-1:]                     #长度为1的切片也会创建一个新的Vector实例
Vector([6.0])
>>> v7[1,2]                     #我们在上面的slice已经知道,如果slice中包含了,则是一个包含slice的元祖,所以报错了
Traceback (most recent call last):
...
TypeError: Vector indices must be integers

Vector类第3版:动态存取属性

  Vector2d 变成 Vector 之后,就没办法通过名称访问向量的分量了(如 v.x 和 v.y)。现在我们处理的向量可能有大量分量。不过,若能通过单个字母访问前几个分量的话会比较方便。比如,用 x、y 和 z 代替 v[0]、v[1] 和 v[2]

  在 Vector2d 中,我们使用 @property 装饰器把 x 和 y 标记为只读特性。我们可以在 Vector 中编写四个特性,但这样太麻烦。特殊方法 __getattr__ 提供了更好的方式。

  属性查找失败后,解释器会调用 __getattr__ 方法。简单来说,对my_obj.x 表达式,Python 会检查 my_obj 实例有没有名为 x 的属性;如果没有,到类(my_obj.__class__ )中查找;如果还没有,顺着继承树继续查找。 如果依旧找不到,调用 my_obj 所属类中定义的__getattr__ 方法,传入 self 和属性名称的字符串形式(如 'x')。

(编辑:安卓应用网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读