Fluent Python: Slice
Pyhton中序列类型支持切片功能,比如list:
>>> numbers = [1, 2, 3, 4, 5] >>> numbers[1:3] [2, 3]
tuple也是序列类型,同样支持切片。
(一)我们是否可以使自定义类型支持切片呢?
在Python中创建功能完善的序列类型不需要使用继承,只要实现符合序列协议的方法就可以,Python的序列协议需要__len__, __getitem__两个方法,比如如下的Vector类:
from array import array class Vector: type_code = 'd' def __init__(self, compoments): self.__components = array(self.type_code, compoments) def __len__(self): return len(self.__components) def __getitem__(self, index): return self.__components[index]
我们在控制台查看下切片功能:
>>> v1 = Vector([1, 2, 3]) >>> v1[1] 2.0 >>> v1[1:2] array('d', [2.0])
在这里我们将序列协议委托给self.__compoments(array的实例),只需要实现__len__和__getitem__,就可以支持切片功能了。
(二)那么Python的切片工作原理又是怎样的呢?
我们通过一个简单的例子来查看slice的行为:
class MySequence: def __getitem__(self, index): return index
>>> s1 = MySequence() >>> s1[1] 1 >>> s1[1:4] slice(1, 4, None) >>> s1[1:4:2] slice(1, 4, 2) >>> s1[1:4:2, 7:9] (slice(1, 4, 2), slice(7, 9, None))
我们看到:
(1)输入整数时,__getitem__返回的是整数
(2)输入1:4表示法时,返回的slice(1, 4, None)
(3)输入1:4:2表示法,返回slice(1, 4, 2)
(4)[]中有逗号,__getitem__收到的是元组
现在我们来仔细看看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', 'star t', 'step', 'stop']
我们看到了熟悉的start, stop, step属性,还有一个不熟悉的indices,用help查看下(Pyhon的控制台是很有价值的工具,我们常常使用dir,help命令获得帮助):
Help on method_descriptor: indices(...) S.indices(len) -> (start, stop, stride) Assuming a sequence of length len, calculate the start and stop indices, and the stride length of the extended slice described by S. Out of bounds indices are clipped in a manner consistent with the handling of normal slices.
这里的indices能用于优雅的处理缺失索引和负数索引,以及长度超过目标序列长度的切片,这个方法会整顿输入的slice元组,把start, stop, step都变成非负数,且落在指定长度序列的边界内:
比如:
>>> slice(None, 10, 2).indices(5) # 目标序列长度为5,自动将stop整顿为5 (0, 5, 2) >>> slice(-1, None, None).indices(5) # 将start = -1, stop = None , step = None 整顿为(4, 5, 1) (4, 5, 1)
如果没有底层序列作为代理,使用这个方法能节省大量时间
上面了解了slice的工作原理,我们使用它重新实现Vector类的__getitem__方法:
from array import array from numbers import Integral class Vector: type_code = 'd' def __init__(self, compoments): self.__components = array(self.type_code, compoments) def __len__(self): return len(self.__components) def __getitem__(self, index): cls = type(self) if isinstance(slice, index): return cls(self.__components[index]) # 使用cls的构造方法返回Vector的实例 elif isinstance(Integral, index): return self.__components[index] else: raise TypeError("{} indices must be integers or slices".format(cls))