美文网首页
Python中的Iterable、Iterator、genera

Python中的Iterable、Iterator、genera

作者: yuanzicheng | 来源:发表于2020-06-23 15:50 被阅读0次

    首先看一个例子:Python中list可迭代(Iterable),但是并不是迭代器(Iterator)。

    from collections.abc import Iterable, Iterator
    a = [1, 2, 3]
    print(isinstance(a, Iterator))  # 结果为 False
    print(isinstance(a, Iterable))  # 结果为 True
    

    1.可迭代对象 Iterable

    根据Python的迭代协议,只要类内部包含了iter这个魔法函数,那么这个类的对象就是可迭代对象。

    from collections.abc import Iterable, Iterator
    class A:
        def __iter__(self):
            pass
    print(isinstance(A(), Iterable))    # 结果为 Frue
    print(isinstance(A(), Iterator))    # 结果为 False
    

    只要是可迭代对象,就可以使用for对其进行遍历。

    for x in A():
        print(x)
    

    但是类A中仅定义了iter函数,并没有具体实现,所以使用for对其进行遍历的时候出现了以下错误。

    TypeError: iter() returned non-iterator of type 'NoneType'
    

    很明显,iter魔法函数需要返回一个迭代器,所以我们对类A稍作修改。

    class A:
        def __iter__(self):
            return iter([1, 2, 3, 4])
    

    再使用for进行遍历的时候,以此打印出1、2、3、4。

    2.迭代器 Iterator

    一个类对象如果要成为迭代器,其类中必须同时包含iternext两个魔法函数。

    from collections.abc import Iterable, Iterator
    class B:
        def __init__(self):
            self.index = 0
            self.list = [1, 2, 3, 4, 5]
        def __iter__(self):
            return self
        def __next__(self):
            if self.index == len(self.list):
                raise StopIteration
            value = self.list[self.index]
            self.index += 1
            return value
    print(isinstance(B(), Iterable))    # 结果为 True
    print(isinstance(B(), Iterator))    # 结果为 True
    

    但直接使用迭代器迭代的时候,如果完成了一轮遍历,就无法迭代了。
    所以,通常在iter函数中返回一个迭代器。

    from collections.abc import Iterable, Iterator
    class B:
        def __iter__(self):
            return MyMyIterator()
            
    class MyIterator(Iterator):
        def __init__(self):
            self.index = 0
            self.list = [1, 2, 3, 4, 5]
    
        def __next__(self):
            if self.index == len(self.list):
                raise StopIteration
            value = self.list[self.index]
            self.index += 1
            return value
            
    
    
    if __name__ == '__main__':
        b = B()
        print(isinstance(b, Iterable))
        print(isinstance(b, Iterator))
        for x in b:
            print(x)
        # 因为__iter__()返回迭代器,所以可以反复遍历
        for x in b:
            print(x)
    

    3.生成器 generator

    3.1 使用生成器表达式创建生成器
    print(type((x for x in range(1, 10))))  # 结果 <class 'generator'>
    print(type((x for x in B())))  # 结果 <class 'generator'>
    
    3.2 使用yeild创建生成器
    def fib(index):
        n, a, b = 0, 0, 1
        while n < index:
            yield b
            a, b = b, a + b
            n += 1
    
    
    if __name__ == '__main__':
        gen = fib(10)
        print(gen)
        print(type(gen))    # 结果 <class 'generator'>
        print(isinstance(gen, Iterable))    # True
        print(isinstance(gen, Iterator))    # True   
    

    从最后的结果可以看到,generator也是Iterator,当然也是Interable(Iterator继承自Iterable)。

    因此,可以将generator作为iter中的函数返回值,来构造一个Iterable。

    将前面的Iterable类A进行改造如下:

    class A:
        def __iter__(self):
            return (x for x in range(1, 5))
    

    或者使用生成器函数

    from collections.abc import Iterable, Iterator
    
    
    def fib(index):
        n, a, b = 0, 0, 1
        while n < index:
            yield b
            a, b = b, a + b
            n += 1
    
    
    class B:
        def __iter__(self):
            return fib(10)
    
    
    b = B()
    print(isinstance(b, Iterable))
    print(isinstance(b, Iterator))
    for x in b:
        print(x)
    
    3.3 send、close、throw

    生成器类中有个3个函数send、close、throw。

    send: 生成器下一次迭代时,调用者向生成器中传递一个值。

    close: 关闭生成器,如果继续迭代则会出现StopIteration异常。

    trow: 主动向生成器抛出异常。

    4.yield

    上文提到了,包含yield的函数就是一个生成器,这实际上跟yield的特性有关。
    yield可以让程序在一次迭代后暂停,下次迭代开始时程序还能在暂停的位置恢复并继续运行,而这刚好和生成器的特征吻合。

    另外,上文提到了生成器的send函数可以使调用者向生成器传递值,而yield刚好能够接收到这个值。

    def gen1():
        x = yield 1
        print('a', x)
        yield test(x)
        print('b', x)
        x = yield 3
        print('c', x)
        x = yield 4
        print('d', x)
    
    
    if __name__ == '__main__':
        g1 = gen1()
        print(g1.send(None))
        print(g1.send(222))
        print(g1.send(333))
        print(g1.send(444))
    

    上面的代码中,生成器迭代一次后,程序停在了yield 1位置;

    然后第二次迭代时,通过send函数传入了值222,此时程序又从yield 1位置恢复,从方便理解的角度可以认为传入的值到了x = yield 1中的yield 1,也就是代码的等号右侧,根据代码逻辑,等号右侧的值赋值给了左侧的x

    需要注意的时,生成器第一次迭代的时候,是无法传入值的,只能send(None)或者使用next()进行首次迭代,因为这个时候程序刚刚运行到yield这里,而不是从yield位置恢复。

    5.yield form

    yield from能够在暂停、恢复程序的同时,将Iterable对象转成generator。

    def gen2():
        yield from [1, 2, 3, 4, 5]
        
    if __name__ == '__main__':
        g2 = gen2()
        print(type(g2))
        for x in g2:
            print(x)
    

    相关文章

      网友评论

          本文标题:Python中的Iterable、Iterator、genera

          本文链接:https://www.haomeiwen.com/subject/mxehfktx.html