Crossin的编程教室

标题: 可迭代对象和迭代器 [打印本页]

作者: 江水滔滔    时间: 2019-1-17 17:39
标题: 可迭代对象和迭代器

for 循环是我们在 Python 里非常常用的一个语法,但你有没有思考过 for 循环是怎样实现的?

如果你以前接触过 C++,应该会知道类似 for (int i = 0; i < 100; i++) 这样的写法,它定义了循环的执行条件 i < 100 以及每次循环结束后执行的语句 i++,而 for 本身只起到让代码重复执行的作用,并没有什么额外的功能。这在 Python 中其实更像是 while 循环:
  1. i = 0
  2. while i < 100:
  3.     # 执行循环代码
  4.     i += 1
复制代码
但 Python 里的 for 循环却不一样。使用 for 时,我们没有额外指定结束条件,也不需要一个用来计数的数值,甚至可以通过一个字符串进行循环。之所以可以这样,是因为 Python 中的迭代器(Iterator)以及可迭代对象(Iterable)。

如果一个对象定义了 __iter__ 和 __next__ 两个方法,它就是一个迭代器。对于迭代器来说,__iter__ 返回的是它自身 self,__next__ 则是返回迭代器中的下一个值,如果没有值了则抛出一个 StopIteration 的异常。关于这点,你可以想象成一个只进不退的标记位,每次调用 __next__,就会将标记往后移一个元素并返回,直到结束。

有了迭代器的概念之后,如果一个对象定义了 __iter__ 和方法,返回一个迭代器对象,那么它就是一个可迭代的对象。

从表现上来说,一个对象可迭代,那么它就可以被 for 循环使用。比如我们经常用到的 list、dict、str 等类型,都是可迭代的,所以也就可以通过 for 循环进行遍历,或者更准确的说:被迭代。

有一点绕,我们再来理一理迭代器(Iterator)和可迭代(Iterable)这两个的差别:
一个迭代器一定是可迭代对象,因为它一定有 __iter__ 方法。反过来则不成立。(事实上,Iterator 就是 Iterable 的子类)迭代器的 __iter__ 方法返回的是自身,并不产生新实例。而可迭代对象的 __iter__ 方法通常会生成一个新的迭代器对象。
__iter__、__next__ 分别对应于 Python 的内置函数 iter() 和 next():比如 iter(aList) 就相当于 aList.__iter__()。

所以关于上述两点,我们可以有以下的例子来验证:

迭代器和可迭代之间的继承关系。

__iter__ 方法返回值的区别。id 相同代表是同一个实例。

明白了上述的概念之后,for 循环的实现就好理解了:
首先 for 循环会调用可迭代对象的 __iter__ 方法,获取相应的迭代器每次循环,将迭代器的 __next__ 方法的返回值赋值给循环变量直到捕获迭代器抛出的 StopIteration 异常,循环结束
再来看个例子:

思考题:想一想为什么迭代器 aListIter 被 for 循环迭代第二次的时候就没有输出了?

既然 __next__ 方法可以自己定义,我们也可以自己实现一个迭代器。比如要输出一个斐波那契数列(每一位数值都是前两位数值之和,原题回复关键字 906),通常的做法是循环,“高级”一点的做法是递归。但我们也可以直接写一个斐波那契迭代器:
  1. # 定义迭代器
  2. class Fibonacci():
  3.     def __init__(self):
  4.         self.a = 0
  5.         self.b = 1
  6.     def __iter__(self):
  7.         return self
  8.     def __next__(self):
  9.         # 结束条件
  10.         if self.b > 100:
  11.             raise StopIteration
  12.         # 更新一次数值
  13.         self.a, self.b = self.b, self.a + self.b
  14.         return self.a
  15. # 创建迭代器
  16. fib = Fibonacci()
  17. # 进行迭代
  18. for f in fib:
  19.     print(f, end=' ')
复制代码
输出:
  1. 1 1 2 3 5 8 13 21 34 55 89
复制代码
这个例子中,我们并没有保存一个序列,只是定义了一种规则,就也可以被迭代。

使用迭代器的好处在于:它是一种延迟操作,即当需要用到的时候才去产生结果。比如对于一个序列来说,如果我们要遍历它,并不需要再一开始就把所有元素都生成好,而是只需要知道每个元素的下一个元素是什么就可以了。这样可以节省很多空间,尤其对于数量很大的集合来说。

如果你不懂迭代器的概念,并不影响在代码中使用 for 循环。但了解之后,你会对代码理解得更透彻,同时这也是为我们后面要讲到的生成器做铺垫。


════

其他文章及回答:

如何自学Python | 新手引导 | 精选Python问答 | Python单词表 | 人工智能 | 爬虫 | 我用Python | requests | 计算机视觉 | 字符播放器 | 一图学Python

欢迎搜索及关注公众号:Crossin的编程教室


作者: TED    时间: 2019-1-18 10:35
中间那个思考的问题:我的理解是迭代器的 __iter__ 方法返回的是自身,并不产生新实例,所以当进行了一轮for后,再进行for也只是第一轮的终点了,就不print了?

我还有个疑问,迭代器for完了之后,怎么让它“重置”,使得重新for也可以正常操作?
作者: crossin先生    时间: 2019-1-18 21:29
TED 发表于 2019-1-18 10:35
中间那个思考的问题:我的理解是迭代器的 __iter__ 方法返回的是自身,并不产生新实例,所以当进行了一轮fo ...

https://www.zhihu.com/question/22023252

没有专门的方法,可以转成list再循环
作者: TED    时间: 2019-1-21 14:53
crossin先生 发表于 2019-1-18 21:29
https://www.zhihu.com/question/22023252

没有专门的方法,可以转成list再循环

嗯 好的  多谢老大




欢迎光临 Crossin的编程教室 (https://bbs.crossincode.com/) Powered by Discuz! X2.5