梳理python的可迭代对象、迭代器、生成器
在Python中这几个概念经常用到,但是也很容易被混淆。写这篇笔记,做个简单的总结。
说在前面的一些结论:
- 迭代器一定是一个可迭代对象,可迭代对象却不一定是迭代器。
- 可迭代对象可以通过
Iter()
函数转化为迭代器。 - 可迭代对象一定可以使用for循环迭代,能使用for循环迭代的不一定是可迭代对象
可迭代对象(Iterable)
先来看可迭代对象, 简单来说在python中的一个对象如果实现了__iter__()
方法(这个方法的具体作用放到后面说),那个这个对象就是可迭代对象(Iterable)。例如常见的可迭代对象有:
- 集合或序列(如
list
、tuple
、set
、dict
、str
) - 文件对象
- 实现了
__iter__()
方法的对象
来验证一下上述提到的是否真的是可迭代对象,并且是否都实现了__iter__()
方法
使用python
内置的isinstance
可以用来验证对象是否是可迭代对象。使用内置hasattr
函数来验证对象是否实现了__iter__
方法。
1 | from collections.abc import Iterable |
迭代器(Iterator)
再来看一下迭代器的概念,简单来说,一个对象实现了 __iter__
和__next__
方法,那么它就是一个迭代器。例如我们在上一段代码中自己实现的可迭代对象Iterable
只要再加上一个__next__
方法,那么它的实例化对象就可以称为一个迭代器。
我们可以使用python
的内置函数next
对迭代器进行调用,只到迭代器抛出异常StopIteration
。例如:
1 | class IterObj(object): |
在上面的代码中我们在构造函数中定义了一个列表,在__iter__
方法中我们返回了iter(self.arr)
。那么这个iter()是个什么东西呢?
__iter__
和iter()
前面已经说过了,如果一个对象实现了__iter__
方法,那么这个对象就是可迭代对象。那这个方法具体做了些什么呢?这个方法,返回的是一个迭代器(Iterator)
.
iter()
这个内置函数返回的也是一个迭代器。它的工作过程是,先访问对象的__iter__
方法,如果不存在则访问__getitem__
方法。这就是结论2 的由来。
那这个Iter()
是给谁用的呢?只有我们显式的调用吗?我们回顾一下对列表的一个常见的操作: 使用for
循环对其进行迭代,在这个过程当中,for
循环的工作过程是这样的:
- 先判断对象是否是为可迭代对象,或者对象是否实现了
__getitem__()
方法(先留个坑待会再填)。如果是的话,则对对象调用Iter
方法,否则抛出异常。所以在我们自己实现的一个可迭代对象中,我们必须要保证__iter__
这个方法返回的是一个迭代器。 - 不断的调用迭代器里的
__next__
方法,返回迭代器里的一个值。 - 迭代的最后,迭代器里没有元素了,就会抛出一个异常,for循环结束。
对于其他的可迭代对象,过程也是一样的。弄明白for
循环的工作原理后,就可以来动手实现一个可迭代对象了。
通常我们可以使用已有的可迭代对象来辅助我们实现自定义的可迭代对象, 也可以通过同时实现__next__
和__iter__
来自定义一个迭代器(Iterator
)。
1 | # 借助已知的可迭代对象来自定义可迭代对象 |
生成器(generator`)
生成器本质上还是一个迭代器。它也可以用在迭代的操作中,它和迭代器的区别是,它是一遍迭代一遍计算。在对生成器调用next()
函数时,生成器才会计算需要返回的值,在这之前需要返回的值是不存在的,而迭代器在调用next()
函数前,返回值已经存在了。
定义生成器有两种方式:
- 列表生成器
- 使用
yield
定义生成器函数
使用列表生成器
1 | gen = (i*i for i in range(3)) # 需要把列表生成器的[]改为() |
使用yield
关键字
举个栗子🌰,斐波那契数列:
1 | def fib(n): |
另外python使用生成器的特点实现了协程,它相对于线程处理高并发场景具有更大的优势。
梳理python的可迭代对象、迭代器、生成器