Python迭代和解析(4):自定义迭代

发布时间:2019-05-05 21:15:30编辑:auto阅读(1815)

    解析、迭代和生成系列文章:https://www.cnblogs.com/f-ck-need-u/p/9832640.html


    本文介绍如何自定义迭代器,涉及到类的运算符重载,包括__getitem__的索引迭代,以及__iter____next____contains__,如果不了解这些知识可跳过本文。

    索引迭代方式

    索引取值和分片取值

    元组、列表、字典、集合、字符串都支持索引取值操作和分片操作。

    >>> L = [11,21,31,41]
    >>> L[0]
    11
    >>> L[0:2]
    [11, 21]

    分片操作实际上将一个slice对象当作索引位传递给序列,然后以索引取值的方式取得所需元素。

    >>> L[0:2]
    [11, 21]
    
    >>> L[slice(0,2)]
    [11, 21]

    slice对象由slice()函数创建,它有3个参数:起始索引位、结束索引位、步进值。例如:

    >>> slice(0,2)
    slice(0, 2, None)

    __getitem__

    列表、元组等序列之所以可以索引取值、分片取值,是因为它们实现了__getitem__方法。

    例如:

    >>> hasattr(list,"__getitem__")
    True
    >>> hasattr(tuple,"__getitem__")
    True
    >>> hasattr(dict,"__getitem__")
    True
    >>> hasattr(str,"__getitem__")
    True

    如果自定义类并实现__getitem__方法,它们会重载索引取值:

    class cls:
      def __getitem__(self, index):
        print("getitem index", index)
        return index * 2
    
    >>> c = cls()
    >>> c[1]
    getitem index 1
    2
    >>> c[2]
    getitem index 2
    4
    >>> c[3]
    getitem index 3
    6

    上面的自定义类只支持索引取值,不支持分片取值。因为__getitem__中没有编写索引取值的方式,也就不支持传递slice对象来进行分片取值。

    分片和__getitem__

    如果想要__getitem__支持分片取值,需要在__getitem__中使用索引取值的方式,以便支持slice对象作为索引。

    下面是一个简单的支持分片操作的自定义类:

    class cls:
      def __init__(self,data):
        self._data = data
      def __getitem__(self,index):
        print("getitem:",index)
        return self._data[index]
    
    >>> c = cls([1,2,3,4])
    >>> c[1]
    getitem: 1
    2
    >>> c[0:2]
    getitem: slice(0, 2, None)
    [1, 2]

    __setitem__和__delitem__

    如果想要索引或者分片赋值,那么会调用__setitem__()方法,如果想要删除索引值或分片值,会调用__delitem__()方法。

    class cls:
      def __init__(self,data):
        self._data = data
      def __getitem__(self,index):
        print("in getitem")
        return self._data[index]
      def __setitem__(self,index,value):
        print("in setitem")
        self._data[index] = value
      def __delitem__(self,index):
        print("in delitem")
        del self._data[index]
      def __repr__(self):
        return str(self._data)
    
    >>> c = cls([11,22,33,44,55])
    >>> c[1:3]
    in getitem
    [22, 33]
    >>> c[1:3] = [222,333]
    in setitem
    >>> c
    [11, 222, 333, 44, 55]
    >>> del c[1:3]
    in delitem

    __getitem__索引迭代

    __getitem__重载了索引取值和分片操作,实际上它也能重载索引的迭代操作。以for为例,它会循环获取一个个的索引并向后偏移,直到超出索引边界抛出IndexError异常而停止。

    此外,__getitem__重载使得它可以被迭代,也就是它通过数值索引的方式让这个对象变成可迭代对象,所有迭代工具(比如zip/map/for/in)都可以对这个对象进行迭代操作。

    class cls:
      def __init__(self,data):
        self._data = data
      def __getitem__(self,index):
        return self._data[index]
      def __repr__(self):
        return str(self._data)
    
    >>> c1 = cls([11,22,33,44,55])
    >>> I = iter(c1)
    >>> next(I)
    11
    >>> 22 in I
    True
    
    >>> I=iter(c1)
    >>> for i in I:print(i,end=" ")
    ...
    11 22 33 44 55

    可迭代对象:__iter____next__

    定以了__getitem__的类是可迭代的类型,是通过数值索引的方式进行迭代的,但这是退而求其次的行为,更好的方式是定义__iter__方法,使用迭代协议进行迭代。当同时定义了__iter____getitem__的时候,iter()函数优先选择__iter__,只有在__iter__不存在的时候才会选择__getitem__

    例如:

    class Squares:
        def __init__(self, start, stop):  # 迭代起始、终止位
            self.value = start
            self.stop = stop
    
        def __iter__(self):     # 返回自身的迭代器
            return self
    
        def __next__(self):     # 返回下一个元素
            if self.value > self.stop:   # 结尾时抛出异常
                raise (StopIteration)
            item = self.value**2
            self.value += 1
            return item
    
    if __name__ == "__main__":
        for i in Squares(1, 5):
            print(i, end=" ")
    
        s = Squares(1,5)
        print()
        print(9 in s)

    运行结果:

    1 4 9 16 25
    True

    因为上面的类中同时定义了__iter____next__,且__iter__返回的是自身,所以这个类型的每个迭代对象都是单迭代的。

    >>> s = Squares(1,5)
    >>> I1 = iter(s)   # I1和I2迭代的是同一个对象
    >>> I2 = iter(s)
    >>> next(I1)
    1
    >>> next(I2)   # 继续从前面的位置迭代
    4
    >>> next(I1)
    9

    自定义多迭代类型

    要定义多迭代的类型,要求__iter__返回一个新的迭代对象,而不是self自身,也就是说不要返回自身的迭代器。

    例如:

    # 返回多个独立的可迭代对象
    class MultiIterator:
        def __init__(self, wrapped):
            self.wrapped = wrapped   # 封装将被迭代的对象
    
        def __iter__(self):
            return Next(self.wrapped) # 返回独立的可迭代对象
    
    # 自身的迭代器
    class Next:
        def __init__(self, wrapped):
            self.wrapped = wrapped
            self.offset = 0
    
        def __iter__(self):
            return self
    
        def __next__(self):   # 返回下一个元素
            if self.offset >= len(self.wrapped):
                raise (StopIteration)
            else:
                item = self.wrapped[self.offset]
                self.offset += 1
                return item    # 返回指定索引位置处的元素
    
    
    if __name__ == "__main__":
        string = "abc"
        s = MultiIterator(string)
        for x in s:
            for y in s:
                print(x + y, end=" ")

    每个for迭代工具都会先调用iter()来获取可迭代对象,然后调用next()获取下一个元素。而这里的iter()会调用MultiIterator的__iter__来获取可迭代对象,而MultiIterator所返回的可迭代对象是相互独立的Next对象,因此for x in xfor y in s所迭代的是不同迭代对象,它们都有记录着自己的迭代位置信息。

关键字