1. 程式人生 > >Fluent Python: Slice

Fluent Python: Slice

des attr_ typeerror 自定義類型 attr self vector -s instance

  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))

Fluent Python: Slice