python 生成器和迭代器介绍
在正式接触生成器之前,我们先来了解一些概念
容器(container
)
容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个迭代获取,可以用in、not in关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特殊的存在)。
Python中常见的容器对象:
- list ,deque, ...
- set, frozensets, ...
- dict, defaultdict, OrderedDict, Counter, ...
- tuple, nametuple, ...
- str
提示:1.可迭代对象赋予容器一种可以供提取元素的能力
2.不是所有的容器都是可迭代的
可迭代对象(iterable)
如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
可迭代对象并不局限于容器,如:files,sockets等(处于打开状态)。只要是可用i返回有一个可迭代的对象都可以称之为可迭代对象。
例:
x = [1, 2, 3] y = iter(x) print(next(y)) print(next(y)) print(type(x), type(y))
分析:
这里x是一个可迭代对象,可迭代对象和容器一样是一种通俗的称呼,并不是指某种具体的数据类型。list,set,dict都是可迭代对象而y则是一个独立的迭代器,迭代器内部有一个状态,该状态用于记录当前迭代坐在的位置,以方便下次迭代的时获取正确的元素。
迭代器有一种具体的迭代器类型:list_iterator,set_iterator, ...。可迭代对象实现了__iter__()和__next__()方法(python2中是next()方法,python3.x是__next__()方法),这两个方法对应内置函数iter()和next()。__iter__方法返回可迭代对象本身,这使得它既有可迭代对象同时也是一个迭代器。
迭代器(iterator)
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来。
迭代器具备以下两种方法:
- __iter__()
- __next__()
而可迭代对象必须具备:
- __iter__()
提示:1.对于迭代器来讲,有一个__next__()就够了。在使用for循环时,程序会自动调用即将被处理的迭代器对象,然后使用next()函数,直到检测到一个StopIteration异常。
2.next()内置函数就是调用对象的方法__next__(),iter()内置函数是调用对象的__iter__()方法。
案例:
- 直接调用next()方法
- 先使用iter(),再调用next()
上面的例子中,我们都知道列表 t 可以用 for 循环进行取值操作,但是不能被内置函数 next() 来取值,因此,判断 t 是 可迭代对象(iterable); 当 t 通过 iter() 进行包装后,可以调用 next() 查询取值,所以,I 是 迭代器(iterator)。
除了上述方式可以判断,还有collections模块的判断函数:
- 可迭代对象判断
- 迭代器判断
使用 iter() 将可迭代对象进行转化成迭代器
小结:我们可以推断,for 循环内部就是先调用 iter() 把 iterable 变成 iterator 再进行循环迭代。
上面的for循环和while循环等价
为什么list, dict, set, str等数据类型不是 迭代器(Iterator)?
因为Python的 Iterator 对象表示的是一个数据流,Iterator对象可以被next()内置函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration异常。可以把这个数据流看做是一个有序序列,但是不能提前知道这个序列的长度,只能不断通过next()内置函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时才会计算。Iterator可以看做是无限大的数据流,eg: 全体自然数,而list,set, str等是不可能存储全部的自然数的。
补充: Iterator继承自Iterable,从下面的测试中可以很方便的看到Iterator包含__iter__()和next()方法,而Iteratble仅仅包含__iter__()。
from collections import Iterator, Iterable
迭代器总结:
- 用for循环迭代的对象都是Iterable类型
- 用next()内置函数取值的对象都是Iterator类型,表示一个惰性计算的序列
- Iterable均可通过内置函数iter(),获得一个Iterator对象
- for循环内部实现机制
生成器
生成器的概念要比迭代器稍显复杂,因为生成器是能够返回一个迭代器的函数,其最大的作用是将输入对象返回为一个迭代器。Python中使用了迭代的概念,是因为当需要循环遍历一个较大的对象时,传统的内存载入方式会消耗大量的内存,不如需要时读取一个元素的方式更为经济快捷。
生成器是一次生成一个值的特殊类型函数(特殊的迭代器)。可以将其视为可恢复函数。调用该函数将返回一个可用于生成连续 x 值的生成器Generator。
有两点要先明确:
- 任意生成器都是迭代器(反之,不成立)
- 任意生成器,都是一个可以延迟创建值的工厂(可控性)
生成器的创建
- 将列表生成式中
[]
改成()
通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含百万元素的列表,不仅是占用很大的内存空间,如:我们只需要访问前面的几个元素,后面大部分元素所占的空间都是浪费的。
因此,没有必要创建完整的列表(节省大量内存空间)。在Python中,我们可以采用生成器:边循环,边计算的机制—>generator
我们该怎么打印元素呢?
刚刚提到生成器是特殊的迭代器,可以通过next()内置函数来获得generator的下一个返回值:
generator是保存的算法,每次调用next()内置函数,才能计算出下一个元素。
next的方式肯定不能用的,正因为generator也是可迭代对象,我们可以使用for循环。
提示:这里使用for循环,没有出现异常情况,是和迭代器一样的,都是因为for循环的内部机制。
简单的说就是在函数的执行过程中,yield语句会把你需要的值返回给调用生成器的地方,然后退出函数,下一次调用生成器函数的时候又从上次中断的地方开始执行,而生成器内的所有变量参数都会被保存下来供下一次使用。
生成器小结:
- 生成器对象就是一种特殊的迭代器,满足迭代器协议,可以调用next()内置函数;对生成器for 循环时,调用iter()方法返回了生成器对象,然后再不断next()迭代,而iter()和next()都是在yield内部实现的。
- 生成器创建方式:常见两种(列表生成式、函数关键字)。