Python - Iterators 与 Generators

什么是Iterators迭代器?

从概念上来说,Iterator就是Python的一种object,其中包含若干元素,同时这些元素可以被一个一个地读取;

从Python的具体实现来说,一个Iterator object需要实现 iterator protocol 迭代器协议,即实现 __iter__() 以及 __next__() 方法。

什么叫Iterable?

直翻过来,我们可以叫可迭代的,这个概念是针对Python中除了Iterator以外的object来说的。

一个Iterable的object,就是可以根据这个object来生成一个Iterator迭代器。

从实现上来说,iterable的object都内置了 __iter__() 方法。

有点绕晕了?不确定这两个方法是干什么用的?别急,我们来看一个for循环的例子:

>>> nums = [1, 2, 3, 4]
>>> for num in nums:
...     print(num)
...
1
2
3
4

for item in [iterable] 是Python中的for循环,相信很多读者都会使用,但是其实Python在这里做了一件事:

  1. Python首先将[iterable]转换成一个 iterator: iterator = iter(iterable):
    • inter() 方法会调用 iterator 内置的 __iter__() 方法来返回一个迭代器;
  2. 接着执行 item = iterator._next_();

如此循环,就可以遍历整一个数组,通过这个例子,我们就会更好的来说明一些概念;

  1. nums数组就是一个 iterable 的object:
    • 其实现了 __iter__() 方法,而这个方法可以将当前的object转换成一个 iterator object
  2. iterator实现了 iterator protocol, 因此可以执行 __next__() 方法来不断访问下一个元素,直到报出 StopIteration 异常;

Figure 1: iter() & iter() & next()

Figure 1: iter() & iter() & next()

现在我们可以总结一下: Iterator,迭代器,就是一种实现了 iterator protocol 的object,实现了 __iter__() 以及 __next__() 两个方法;

Iterable的object,就是可以被 iter() 方法调用其 __iter__() 方法,将其转换为一个Iterator object的object:

  • Sequences, 例如内置的lists, dictionaries, tuples, sets以及files;
    • Sequences的特点就是可以用 integer index 来访问,例如array[0], 而generators就不可以;
  • Generators, 生成器;
  • 任意自定义的,实现了 __iter__() 的classes;

而python中的for循环, for item in [iterable] 就是:

  1. 先利用 iter() 将iterable object转换成 iterator object;
  2. 接着使用 iterator.next() 不断获取元素;

可以换一种实现手法:

iterator = iter(iterable)
while item := iterator.__next__():
    print(item)

结果如下:

>>> iterator = iter(nums)
>>> while item := iterator.__next__():
...     print(item)
...
1
2
3
4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

什么是Generators?

在上一节的学习中,我们在总结Iterable object提到了 Generators 这个名词,顾名思义,生成器就是在循环迭代的过程中,动态地生成需要的元素;

这是什么意思呢?

我们继续用上面的例子,我们要循环遍历一个list,就需要将这个list转成 iterator 来进行循环,那么,如果这个list很大,所生成的需要在循环中所维护的内容也就会占据很大的内存空间,有些情况下效率就不能保证。

而Generators就可以很好的解决这个问题, generator 作为一种 iterable object,它在循环的过程中不需要保存所有的值,而是随取随用,是哟中惰性计算( lazy evaluation)。

创建Generators

我们有两种方法来创建一个 generators object:

  1. 创建一个 Generator Function 来返回一个 generator object:

    >>> def create_gen():
    ...     n = 1
    ...     print('第一次被调用')
    ...     yield n
    ...
    ...     n += 1
    ...     print('第二次调用')
    ...     yield n
    ...
    ...     n += 1
    ...     print('第三次调用')
    ...     yield n
    ...
    >>> gen = create_gen()
    >>> gen
    <generator object create_gen at 0x7fbbd8199ac0>
    >>> for item in gen:
    ...     print(item)
    ...
    第一次被调用
    1
    第二次调用
    2
    第三次调用
    3
    
    • 我们定义了一个方法 create_gen(), 其中包含了 yield 表达式,最终返回了一个 generator object:
      • yield表达式与return类似,都会返回一个值,不同的是:
        • return的返回伴随着方法的结束;
        • 而yield在返回之后,这个被调用的方法将保存所有的状态,进入等待状态,等待下一次被调用;
        • 我们可以说当一个方法内部出现 yield 表达式,这个方法就是一个 Generator Function;
  2. 第二种方法是使用 Generator Expression

>>> gen = (x*x for x in range(5))
>>> gen
<generator object <genexpr> at 0x7fbbd8199b30>
>>> for item in gen:
...     print(item)
...
0
1
4
9
16
  • Generator Expression与我们以前所使用的 List comprehension 很类似,区别在于list comprehension 列表解析外面包裹的是 [], 而这里使用的是 ()
  • Generator Expression将会返回一个 generator object 来支持循环

为什么我们要使用Generators:

  1. 便于实现一个iterable object:比如我们的for循环现在需要一个 iterable object, 要求包含一串2的平方的数:
    • 自定义的iterable类,就需要实现 __iter__() 以及 __next__() 方法:

      class PowTwo:
          def __init__(self, max=0):
              self.n = 0
              self.max = max
      
          def __iter__(self):
              return self
      
          def __next__(self):
              if self.n > self.max:
                  raise StopIteration
      
              result = 2 ** self.n
              self.n += 1
              return result
      
    • 而使用Generator就可以省力不少:

      def PowTwoGen(max=0):
          n = 0
          while n < max:
              yield 2 ** n
              n += 1
      
  2. 更高效地内存使用:
    • 传统的内置 iterable object 例如list,tuple,在生成Iterators生成器时,需要将所有的数据放到 iter() 方法中去,如果数据很大,内存不够了,就会很麻烦;
    • 而generator放在 iter() 中的仅仅只是一个 iterator object 并不会占用很多的内存空间
  3. 用来表示无穷的流:
    • 我们知道,Generators在调用了 yield 之后就会保存所有的状态并进入等待状态;

    • 那么就可以实现一个无限循环,只需要等待有需要的时候再调用就可以了,比如下面就是一个无限循环的递增生成器:

      def gen():
          n = 0
          while True:
              yield n
              n += 1
      
  4. 嵌套使用Generators
    • 比如我们要实现一个功能,将一组斐波那契数列(从数列第3项开始,每一项都等于前两项之和)的每个数都平方后求和:

      def fibonacci_numbers(nums):
          x, y = 0, 1
          for _ in range(nums):
              x, y = y, x+y
              yield x
      
      def square(nums):
          for num in nums:
              yield num**2
      
      print(sum(square(fibonacci_numbers(10))))
      

拓展阅读

Python 中生成器的原理

Reference

[1] e-satis, ‘Answer to “What does the ‘yield’ keyword do?”’, Stack Overflow, Oct. 23, 2008. https://stackoverflow.com/a/231855/17534765.

[2] user28409, ‘Answer to “What does the ‘yield’ keyword do?”’, Stack Overflow, Oct. 25, 2008. https://stackoverflow.com/a/237028/17534765.

[3] ‘Python Iterators (iter and next): How to Use it and Why?’ https://www.programiz.com/python-programming/iterator. [4] ‘Python 中生成器的原理 - zikcheng - 博客园’. https://www.cnblogs.com/zikcheng/p/16462284.html.

[5] ‘Python的generator_Wanderer001的博客-CSDN博客_generator函数 python’. https://blog.csdn.net/weixin_36670529/article/details/103941740.

Licensed under CC BY-NC-SA 4.0
Last updated on Nov 05, 2022 18:46 CST
comments powered by Disqus
Cogito, ergo sum
Built with Hugo
Theme Stack designed by Jimmy