Python面试题系列之04: 什么是迭代器、生成器?
Question
什么是迭代器、生成器?
知识点详解
1 迭代器
假如我们自己写了一个数据类型,希望这个数据类型里的东西也可以使用for循环被一个一个地取出来,那我们就必须满足for的要求。这个要求就叫做“协议”。
可以被迭代要满足的要求就叫做可迭代协议。可迭代协议的定义非常简单,就是内部实现了iter方法。
1.1 什么是可迭代对象(Iterable)
如果这个对象中有__iter__()
方法,这个对象就是可迭代对象。简单来说就是可以使用for...in...
语句进行循环的对象。我们可以使用isinstance()
方法进行判断。
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('Pythonista', Iterable)
True
>>> isinstance((x for x in range(9)), Iterable)
True
>>> isinstance(100, Iterable)
False
1.2 什么是迭代器(Iterator)
迭代器协议是指:对象必须提供一个next
方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration
异常,以终止迭代。
迭代器只不过是一个实现迭代器协议的容器对象,它基于两个方法:
next()
方法,返回容器的下一个项目iter()
方法,返回迭代器本身
>>> temp = iter(['人人都是', 'Pythonista'])
>>> print(type(temp))
<class 'list_iterator'>
>>> print(next(temp))
人人都是
>>> print(next(temp))
Pythonista
>>> print(next(temp))
Traceback (most recent call last): StopIteration
1.3 迭代器的优势及使用场景
在构建迭代器时,不是将所有的元素一次性的加载,而是等调用next
方法时返回元素,所以不需要考虑内存的问题。
所以迭代器常用于如下场景中:
- 数列的数据规模巨大
- 数列有规律,但是不能使用列表推导式描述。
2. 生成器
2.1 什么是生成器(Generator)
生成器类似于一种数据类型,这种数据类型自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__
方法)。生成器在Python中的表现形式分为生成器函数、生成器表达式两种。
2.1 生成器函数
生成器函数和常规函数一样,都采用def
语句进行定义。但在返回结果时,生成器函数中使用的是yield
语句而不是return
语句。yield
语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行。
当需要一个将返回一个序列或在循环中执行的函数时,就可以使用生成器,因为当这些元素被传递到另一个函数中进行后续处理时,一次返回一个元素可以有效的提升整体性能。
2.2 生成器表达式
生成式表达式,类似于列表推导。它只是按需返回产生结果的一个对象,而不是构建一个结果列表,即按需取出对象。
生成式表达式是一种实现生成器的便捷方式,将列表推导式的中括号替换为圆括号。
>>>s = [x ** 2 for x in range(5)]
>>>s
[0, 1, 4, 9, 16]
>>>g = (x ** 2 for x in range(5))
>>>g
<generator object <genexpr> at 0x000001B670D4A408>
>>>next(g)
0
>>>next(g)
1
生成器表达式是一种边循环边计算,使得列表的元素可以在循环过程中一个个的推算出来,不需要创建完整的列表,从而节省了大量的空间。
2.3 关于生成器的总结
- 语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用
def
语句进行定义,差别在于生成器使用yield
语句返回一个值,而常规函数使用return
语句返回一个值。 - 自动实现迭代器协议:由于生成器自动实现了迭代器协议,所以我们可以调用它的
next
方法,并且在没有值可以返回的时候,生成器自动产生StopIteration
异常。 - 状态挂起:生成器使用
yield
语句返回一个值。yield
语句挂起该生成器的状态,保留足够的信息,以便之后从它离开的地方继续执行。 - 生成器只能遍历一次,不能重复使用:在程序中如果已经使用生成器遍历过一次得到结果,那么第二次遍历生成器将得不到任何结果,因为在第一次遍历生成器时,生成器已经遍历到头,它的特性决定它无法遍历第二次。
Answer
迭代器
可被next()
函数调用并不断返回下一个值的对象就是迭代器。简单来说:凡是可以被for循环访问的都是迭代器,这是因为迭代器里面实现了iter协议
和next方法
。
生成器
生成器就是特殊的迭代器,它在迭代器的基础上再次进行了封装。
因为自动创建了iter()
和next()
方法,使得生成器显得特别简洁;使用生成器表达式取代列表解析可以同时节省内存,所以生成器也很高效。
另外生成器是协程的基础,协程是通过上下文切换进行任务协作的,而生成器挂起状态的特性很好地满足了协程的需求。
后记
最后再通过这张图来理解生成器(Generator)、迭代器(Iterator)、可迭代对象(Iterable)之间关系。欢迎大家在评论区留言说出自己的理解。
好了,以上就是本篇全部内容。
备注:本篇首发于知识星球「人人都是Pythonista」。