美文网首页
学习python——迭代器

学习python——迭代器

作者: 天命_风流 | 来源:发表于2020-02-03 01:55 被阅读0次
    迭代器的方方面面

    迭代是一个非常有趣的概念,新计算的基础来源于旧的计算结果,同时新的结果将会成为下一次计算的基础。
    在python中有很多工具来支持迭代的思想,最具代表性的就是迭代器。下面我将介绍关于迭代的方方面面。

    认识迭代

    说起迭代,最先想到的就是for循环:

    for i in [1,2,3,4,5]:
        print( i**2 )
    

    1
    4
    9
    16
    25

    这就是迭代的最常见的应用,不仅是列表,python中大部分的内置数据结构都支持迭代,像这种可以使用for循环调用的东西,我们将之成为可迭代对象
    在可迭代对象中,有一个方法iter可以构造出一个迭代器,在for循环的工作中,有一部分内容就是使用这个方法构造出一个迭代器(迭代器本身就是一个可迭代对象)。在构造出迭代器之后,for循环会自动对其进行迭代。

    • 使用iter(f)或者f.iter()可以手动构造迭代器
    • 使用 next(f)或者f.next( )可以手动对迭代器进行迭代
    • 当迭代器走到尽头(手动)的时候会触发异常:StopIteration
    i = [1,2,3].__iter__()
    i.__next__()
    Out[44]: 1
    i.__next__()
    Out[45]: 2
    i.__next__()
    Out[46]: 3
    i.__next__()
    Traceback (most recent call last):
      File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-47-aa5506447029>", line 1, in <module>
        i.__next__()
    StopIteration
    

    上面的代码使用列表构造了一个迭代器,并且手动迭代至结束。

    • 迭代器有点愣,它只能向前走,不会回头
    • 迭代器每向前迭代一步,上一步的内容就会消失
    i = [1,2,3,4,5].__iter__()
    i.__next__()
    Out[57]: 1
    i.__next__()
    Out[58]: 2
    list(i)
    Out[59]: [3, 4, 5] 
    

    可以看到,上面的迭代器转换成列表之后,没有已经迭代过的内容

    python中关于迭代的内容

    上面的例子拆解了列表生成迭代器的过程,你可以自己尝试对字典、集合、字符串、文件句柄等数据类型进行迭代,除此之外,map、zip、range、enumerate等函数会直接产生迭代器,你可以试试看会有什么效果。
    在这里我想说一说列表解析式

    列表解析式

    列表解析式利用迭代原理构造列表,这个东西很有意思,某些时候写起来也非常方便,同时它的执行性能非常快(由C完成)

    [ i * 2 for i in '123' ]
    Out[65]: ['11', '22', '33']
    

    如上面的代码所示,列表解析式可以很方便的构造列表

    • 列表解析式中可以实现for 的嵌套
    [ x + y for x in 'abc' for y in '123']
    Out[64]: ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']
    
    • 列表解析式可以实现if的判断
    [ i for i in [0,1,2,3,4,5,6,7,8,9] if i % 2 == 0 ]
    Out[66]: [0, 2, 4, 6, 8]
    

    共享迭代进度

    迭代器也可以使用iter(f)或者f.iter()构造迭代器

    • 由迭代器构造的迭代器共享同样的迭代进度
    a = [1,2,3,4,5,6,7,8,9]
    i = a.__iter__()
    i.__next__()
    Out[76]: 1
    i.__next__()
    Out[77]: 2
    ii = i.__iter__()      #使用迭代器构造迭代器
    ii.__next__()
    Out[79]: 3        #发现两个迭代器的进度相同
    ii.__next__()
    Out[80]: 4
    
    • 任意一个迭代器结束,共享进度的迭代器也会结束
    #接上面的代码,同时省略一些步骤...
    ii.__next__()
    Out[85]: 9
    ii.__next__()    #  迭代器ii已经结束
    Traceback (most recent call last):
      File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-86-090408da02bb>", line 1, in <module>
        ii.__next__()
    StopIteration
    i.__next__()    #迭代器  i 也结束了
    Traceback (most recent call last):
      File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-87-aa5506447029>", line 1, in <module>
        i.__next__()
    StopIteration
    

    构造自己的迭代器

    使用生成器构造迭代器

    • 生成器可以生成迭代器,使用yield关键字控制迭代进度
    • 在发出下一次迭代的信号前,生成器构造的迭代器将处于挂起状态挂起位置为本次yield的位置
    • 由生成器构成的迭代器推进到没有yield且函数执行完毕时,会抛出StopIteration异常
    def gen():
        for i in [1,2,3]:
            yield i
        
    i = gen()  #使用生成器构造迭代器
    i.__next__()
    Out[90]: 1
    i.__next__()
    Out[91]: 2
    i.__next__()
    Out[92]: 3
    i.__next__()    #迭代器走到尽头
    Traceback (most recent call last):
      File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-93-aa5506447029>", line 1, in <module>
        i.__next__()
    
    • 由生成器构造的迭代器可以使用 i.send()接收参数
    • send( ) 会触发下一次迭代,如果使用使用__next __进行迭代,默认传出的参数为None
    • 接收的参数为yield左边的变量,具体请看代码
    • 请注意看下面的例子,并理解生成器将挂起于yield的位置的含义
    def gen():
        print('start')
        p1 = yield 1    #第一次迭代将在 yield 1 处挂起,此时 p1 还没有被赋值
        print(p1)
        p2 = yield 2   # 第二次迭代将在 yield 2 处挂起,此时 p2 没有被赋值, p1 = '我是p1'
        print(p2)
        print('over')
    i = gen()    # 构建一个生成器
    i.__next__()  # 第一次迭代必须使用__next()__
    start  # print('start')
    Out[96]: 1  #挂起于 yield 1 的位置,此时 p1 没有被赋值
    
    i.send('我是p1')    #使用send( )为p1赋值,同时开启下一次迭代
    我是p1   # print(p1)
    Out[97]: 2  # 在 yield 2 的地方挂起
    i.send('我是p2')  # 开启下一次迭代
    我是p2  # print(p2)
    over
    Traceback (most recent call last):    #走到尽头,触发异常
      File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-98-015be98d3e32>", line 1, in <module>
        i.send('我是p2')
    StopIteration
    

    生成器表达式

    生成器表达式会直接生成一个迭代器,其使用方法和列表解析式如出一辙,只不过使用()替代了 [ ]

    i = ( i ** 2 for i in range(3) )
    i
    Out[101]: <generator object <genexpr> at 0x00000186AB42A410>
    i.__next__()
    Out[102]: 0
    i.__next__()
    Out[103]: 1
    i.__next__()
    Out[104]: 4
    i.__next__()
    Traceback (most recent call last):
      File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-105-aa5506447029>", line 1, in <module>
        i.__next__()
    StopIteration
    
    • 生成器表达式的输出是迭代和计算的结果,而不使用内存来保存数据,这是它和列表解析式的最大区别

    使用类构造可迭代对象

    使用__getitem __ 方法

    • 这个方法会重载索引
    • for 语句可以为对象提供从 0 到更大的索引值的索引,重复索引运算,直到检测出超出边界异常
    • 所以__getitem __ 方法本质上不是提供迭代的方法,但是可以在for循环中实现相同的效果
    class gen:
        def __init__(self):
            self.data = [1,2,3]
        def __getitem__(self, item):
            return self.data[item]
    g = gen()
    for i in g:
        print(i)
    

    1
    2
    3

    使用__iter __ 和 __next __ 方法

    • __iter __会返回一个迭代器
    • 迭代器的迭代过程本质上是执行__next __ 的过程
    • 在迭代环境中,python会先使用__iter __ 获取一个迭代器,然后使用__next __不断迭代
    • 使用__iter __ 和 __next __ 可以构造可迭代对象和迭代器对象
    • 这种方法构造迭代器的优先级高于使用索引的优先级
    class gen:
        def __init__(self):
            self.data = [1,2,3]
            self.index = 0
        def __iter__(self):
            return self
        def __next__(self):
            if self.index < len(self.data):
                self.index += 1
                return self.data[self.index-1]
            else:
                raise StopIteration
    
    a = gen()
    for i in a:
        print(i)
    

    1
    2
    3

    注意__contains __

    • __contains __重载了在 in 后面的行为
    • 判断一个元素是否属于某个结构时使用的 in 也是迭代环境,如 1 in [1,2.3]
    • 在这种情况下先尝试__contains __,然后再尝试使用__iter __
    • 我在下面的综合示例中会用到它

    综合示例

    下面一段代码使用了上面的所有可以应对迭代环境的重载方法,也将会展现出在迭代环境下各个方法加载的优先级:

    class Iters:
        def __init__(self, value):
            self.data = value
    
        def __getitem__(self, item):
            print('get[%s]:' % item, end=' ')
            return self.data[item]
    
        def __iter__(self):  # 将这个方法注释掉,看看会发生什么
            print('iter=> ', end=' ')
            self.ix = 0
            return self
    
        def __next__(self):
            print('next', end=' ')
            if self.ix == len(self.data): raise StopIteration
            item = self.data[self.ix]
            self.ix += 1
            return item
    
        def __contains__(self, item):  # 将这个方法注释掉,看看会发生什么
            print('contains:', end=' ')
            return item in self.data
    
    
    X = Iters([1, 2, 3, 4, 5])
    print(3 in X)
    for i in X:
        print(i, end='|')
    print()
    print([i ** 2 for i in X])
    print(list(map(bin, X)))
    
    I = iter(X)
    while True:
        try:
            print(next(I), end='@')
        except StopIteration:
            break
    
    

    执行结果:

    contains: True
    iter=> next 1|next 2|next 3|next 4|next 5|next
    iter=> next next next next next next [1, 4, 9, 16, 25]
    iter=> next next next next next next ['0b1', '0b10', '0b11', '0b100', '0b101']
    iter=> next 1@next 2@next 3@next 4@next 5@next

    将__contains __注释掉:

    iter=> next next next True
    iter=> next 1|next 2|next 3|next 4|next 5|next
    iter=> next next next next next next [1, 4, 9, 16, 25]
    iter=> next next next next next next ['0b1', '0b10', '0b11', '0b100', '0b101']
    iter=> next 1@next 2@next 3@next 4@next 5@next

    将__contains __、__iter __和__next __都注释掉

    get[0]: get[1]: get[2]: True
    get[0]: 1|get[1]: 2|get[2]: 3|get[3]: 4|get[4]: 5|get[5]:
    get[0]: get[1]: get[2]: get[3]: get[4]: get[5]: [1, 4, 9, 16, 25]
    get[0]: get[1]: get[2]: get[3]: get[4]: get[5]: ['0b1', '0b10', '0b11', '0b100', '0b101']
    get[0]: 1@get[1]: 2@get[2]: 3@get[3]: 4@get[4]: 5@get[5]:

    结论:迭代优先级:__contains __ > __iter __ > getitem

    上面就是迭代器的理论知识了,迭代的特性是非常有趣的,希望可以在编码中用到它

    相关文章

      网友评论

          本文标题:学习python——迭代器

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