Python 可迭代, 迭代器和生成器对比

你是否分清楚过 iterable, iterator, generator? 他们分别都是什么, 互相之间存在什么关系, 都具有哪些用途和如何创建? 本文一一详解.

Iterable 可迭代

任何可以循环遍历的对象都是 iterable, 简单来说, 可以被用在 for loop 中的对象均是, 如:

  • 所有的序列类型都是iterable
    • 字符串 str
    • 列表 list
    • 元祖 tuple
    • 字节对象 bytes
    • 数组 array.array
    • 内存视图 memoryview
    • 字节数组 bytearray 等等
  • 部分非序列类型是 iterable
    • 字典 dict
    • 文件对象 file object
    • 自定义类的对象, 需要实现 __iter__() 或者 __getitem__() 方法

Iterator 迭代器

iterator 是数据流对象, 指那些真正去执行迭代行为的对象.

  • 所有的 iterator 均是 iterable
  • 对 iterable 对象执行了 iter() 后是 iterator
  • iterator 对象中必须实现 _iter_() 方法
    • 该方法返回 iterator 对象, 即它本身
    • 正因为如此, 所以 iterator 才是 iterable 的
  • iterator 对象中必须实现 _next_() 方法
    • 每次对iterator对象调用 next() 方法, 依次返回数据流中的下一个元素
    • 当数据流中的元素都被遍历完成后, 再次调用 next() 会抛出 StopIteration 异常. 在 for 循环中, 可以自动接收异常并退出循环.
# 快速创建一个 iterator
>>> a = [1, 2, 3]
>>> a_iterator = iter(a)
>>> a_iterator
<list_iterator object at 0x105e6eee0>
# 包含 __iter__ 和 __next__ 方法
>>> dir(a_iterator)
['__iter__', '__next__', '__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
>>> next(a_iterator)
0
>>> next(a_iterator)
1
>>> next(a_iterator)
2
>>> next(a_iterator)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

复制代码

Iterator 的优点

迭代器可以让你从第一个开始, 使用next(), 可以一直遍历到最后一个元素. 一个不落地遍历一遍. 使用迭代器的好处是不要求事先准备好迭代过程中的所有元素. 仅在遍历到某个元素的时候, 才去计算当前元素, 是一种懒加载模式.

  • 节省空间

创建一个 10 的 9 次方数量级的迭代器, 仅需要 48 bytes, 而创建一个同样数量级的list, 就需要 8 GB.

>>> from itertools import repeat
>>> import sys
>>> iters = repeat(1, times=10**9)
>>> arry = [1] * 10**9
>>> sys.getsizeof(iters)
48
>>> sys.getsizeof(arry)
8000000056
复制代码

如何创建 Iterator

按照迭代器的定义, 我们可以用面向对象的方法去创建一个迭代器.

步骤:

  1. 实现 _iter_() 方法, 返回迭代器
  2. 实现 _next_() 方法, 此方法返回下一个可以使用的数据, 直到没有数据, 抛出 StopIteration 异常
class IterableFile(object):
    files = ['input.txt', 'data.csv', 'test.csv']

    def __init__(self):
        self.idx = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.idx >= len(self.files):
            raise StopIteration()
        next_file = self.files[self.idx]
        self.idx += 1
        return next_file

复制代码

通过面向对象的方式, 可以给自定义的类编程一个迭代器, 这个方式看起来挺酷的, 但是这不是我们通常创建迭代器的方式. 最常用的方法, 就是创建一个generator生成器.

Generator 生成器

generator 是创建一个 iterator 最便捷的方式. 生成器其实是一种特殊的迭代器,但是不需要像迭代器一样实现__iter__和__next__方法.

创建 generator 一共有两种方式.

生成器表达式(Generator Expression)

生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表. 生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而已.

>>> mylist = [x*x for x in range(3)]
>>> mylist
[0, 1, 4]
>>> mygenerator = (x*x for x in range(3))
>>> mygenerator
<generator object <genexpr> at 0x102ebcf20>
复制代码

yield 表达式

如果一个函数包含 yield 表达式,那么它是一个生成器函数;调用它会返回一个特殊的迭代器,称为生成器。

>>> def count(start=0):
...     num = start
...     while True:
...         yield num
...         num += 1
>>> c = count()
>>> c
<generator object count at 0x10e04e870>
>>> next(c)
0
>>> next(c)
1
复制代码
  • yield 是一个类似 return 的关键字,迭代一次遇到yield时就返回yield后面(右边)的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行.

总结

这篇文章提到了多个概念, 可迭代, 迭代器, 生成器. 它们之间是什么关系, 让我们通过一张图来强化一下~

9003286c1d313105d17a5abbe0fb7ba5.jpg

参考资料

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享