作者:华校专
email: huaxz1986@163.com
** 本文档可用于个人学习目的,不得用于商业目的 **
迭代器和生成器
1.可迭代对象:在逻辑上它保存了一个序列,在迭代环境中依次返回序列中的一个元素值。
可迭代对象不一定是序列,但是序列一定是可迭代对象
2.迭代协议:.__next__()
方法。
- 任何对象只要实现了迭代协议,则它就是一个迭代器对象
- 迭代器对象调用
.__next__()
方法,会得到下一个迭代结果 - 在一系列迭代之后到达迭代器尾部,若再次调用
.__next__()
方法,则会触发StopIteration
异常 - 迭代器在Python中是用C语言的速度运行的,因此速度最快
3.Python3提供了一个内置的next()
函数,它自动调用迭代器的.__next__()
方法。即给定一个迭代器对象x
,next(x)
等同于x.__next__()
4.内置的iter()
函数用于从序列、字典、set
以及其他可迭代对象中获取迭代器。
- 对任何迭代器对象
iterator
,调用iter(iterator)
返回它本身 - 迭代器对象实现了迭代协议
- 文件对象本身是一个迭代器对象。即文件对象实现了迭代协议,因此打开多个文件会返回同一个文件对象
列表、元组、字典、
set
、字符串等不适迭代器对象,他们没有实现迭代协议。因此每次调用iter()
均返回一个新迭代器对象。他们支持安装多个迭代器,每个迭代器状态不同- 在原地修改列表、
set
、字典时,会实时反映到它们的迭代器上
- 在原地修改列表、
5.文件迭代器:文件对象本身是一个迭代器(这里文件对象要是读打开)。它的.__next__()
方法每次调用时,返回文件中的下一行。当到达文件末尾时,.__next__()
方法会引发StopIteration
异常。
.readline()
方法在到达文件末尾时返回空字符串
6.字典的迭代器:字典的迭代器在迭代环境中,每次迭代返回的是一个键。
7.扩展的列表解析表达式:
[ x+y for x in 'abc' if x!='a' for y in 'lmn' if y!='l']
其通用结构为:
[ expression for target1 in iterable1 [if condition1]
for target2 in iterable2 [if condition2]
....]
我们总是可以用
for
循环手动构建列表解析表达式的结果,但是列表解析表达式性能更好
8.for
循环、列表解析、in
成员关系测试、map()
内置函数、sorted()
内置函数、zip()
内置函数等都是用迭代协议来完成工作
9.常见的迭代函数:
map(func,iterable)
:它将函数func
应用于传入的迭代器的每个迭代返回元素,返回一个新的迭代器,函数执行结果作为新迭代器的迭代值map()
可以用于多个可迭代对象:map(func,[1,2,3],[2,3,4])
,其中func(first,second)
的两个参数分别从两个可迭代对象中获取,函数结果作为新迭代器的迭代值zip(iterable1,iterable2,...)
:它组合可迭代对象iterable1
、iterable2
、...
中的各项,返回一个新的迭代器。新迭代器长度由iterable1
、iterable2
、...
最短的那个决定。enumerate(iterable,start)
:返回一个迭代器对象,它迭代结果是每次迭代返回一个(index,value)
元组filter(func,iterable)
:返回一个迭代器对象,它的迭代结果得到iterable
中部分元素,其中这些元素使得func()
函数返回为真reduce(func,iterable,initial)
:对iterable
中每一项成对地运行func
,返回最终值reduce
函数位于functools
包内sorted(iterable,key=None,reverse=False)
:排序并返回排好的新列表sum(iterable,start)
:返回可迭代对象中的累加值any(iterable)
:只要可迭代对象iterable
迭代返回的某个元素为真则返回True
all(iterable)
:只有可迭代对象iterable
迭代返回的所有元素为真则返回True
max(iterable,key=func)
:返回最大元素。若指定func
,则返回是func(num)
最大的那个元素min(iterable,key=func)
:返回最小元素。若指定func
,则返回是func(num)
最小的那个元素
10.set
解析、字典解析支持列表解析的扩展语法
11.Python3中,range
对象不支持.__next__()
,因此它本身不是迭代器,而map
、zip
、filter
对象都是迭代器。
range
不直接生成列表的优点是节约内存空间- 由于
map
、zip
、filter
对象都是迭代器,因此不支持在它们身上安装多个活跃的迭代器。对他们调用iter()
其实返回的是它们本身。
12.字典的视图:键视图、值视图、字典视图都没有.__next__()
方法,因此他们都不是迭代器
13.通常列表解析执行速度最快,map()
速度次之,for
循环速度最慢。前两者以C语言速度执行、后者在Python虚拟机上执行
14.生成器函数:编写为常规的def
语句,但是用yield
语句一次返回一个结果。每次使用生成器函数时会继续上一轮的状态。
生成器函数会保存上次执行的状态
定义生成器函数的语法为:
def genFunc(num):
for i in range(num):
yield i**2
- 生成器函数执行时,得到一个生成器对象,它
yield
一个值,而不是返回一个值。- 生成器对象自动实现迭代协议,它有一个
.__next__()
方法 - 对生成器对象调用
.__next__()
方法会继续生成器函数的运行到下一个yield
结果或引发一个StopIteration
异常
- 生成器对象自动实现迭代协议,它有一个
yield
语句会挂起生成器函数并向调用者发送一个值。当下一轮继续时,函数会在上一个yield
表达式返回后继续执行,其本地变量根据上一轮保持的状态继续使用- 生成器函数运行代码随时间产生一系列的值,而不是一次性计算它们。这会节约内存并允许计算时间分散。
15.for
循环(及其它迭代环境)通过重复调用.__next__()
方法直到捕获一个异常。若一个不支持迭代协议的对象想用工作在这种环境中,for
循环会尝试使用索引协议迭代。
16.生成器对象有一个.send(arg)
方法。该方法会将arg
参数发送给生成器作为yield
表达式的返回值,同时生成器会触发生成动作(相当于调用了一次.__next__()
方法。
yield
表达式的返回值和生成值是不同的。
返回值是用于生成器函数内部,yield
表达式默认返回值为None
;
而生成值是用于生成器函数外部的迭代返回。
- 生成器对象必须先启动。启动意味着它第一次运行到
yield
之前挂起 - 要想启动生成器,可以直接使用
next(generatorable)
函数,也可以使用generatorable.send(None)
方法,或者generatorable.__next__()
方法next(generatorable)
函数相当于使用generatorable.send(None)
方法 generatorable.send(None)
方法会在传递yield
表达式的值(默认为None
返回值),下一轮迭代从yield
表达式返回开始每一轮挂起时,
yield
表达式 yield 一个数,但是并没有返回(挂起了该yield
表达式)
17.生成器表达式:类似于列表解析,但是它是在圆括号中的,而不是方括号中的。
- 生成器表达式返回的是一个生成器对象
- 列表解析的结果等同于
list()
内参数为一个生成器表达式 - 当生成器表达式在其他括号之内时,它本身的圆括号可以不写
- 同样的迭代可以用生成器函数或者一个生成器表达式
- 生成器函数可以包含更多的逻辑
- 生成器表达式更简洁,没有函数调用产生生成器的过程
- 生成器函数与生成器表达式支持自动迭代与手动迭代
- 生成器对象本身是迭代器,因此只支持一个活跃的迭代器。
18.生成器函数可以有return
,它可以出现在函数内任何地方。生成器函数内遇到return
则触发StopIteration
异常,同时return
的值作为异常说明
19.可以调用生成器对象的.close()
方法强制关闭它。这样再次给它send()
任何信息,都会抛出StopIteration
异常,表明没有什么可以生成的了
20.yield from
:从PEP 380
引入的新特性。
yield from
可以将一个大的生成器切分成小生成器:def generator(): #该生成器yield数字[0~19]
for i in range(10):
yield i
for j in range(10,20):
yield j
如果你想切分成两个迭代器,可以这么做:
def generator2():
for i in range(10):
yield i
def generator3():
for j in range(10):
yield j
def generator():
for i in generator2():
yield i
for j in generator3():
yield j
引入
yield from
之后你可以这么做:def generator2():
for i in range(10):
yield i
def generator3():
for j in range(10):
yield j
def generator():
yield from generator2()
yield from generator3()
yield from
能实现代理生成器:def generator():
inner_gen=generator2()
yield from inner_gen #为了便于说明,这里分两行写
gen=generator()
- 对
inner_gen
迭代产生的每个值都直接作为gen
yield值 - 所有
gen.send(val)
发送到gen
的值val
都会被直接传递给inner_gen
。 inner_gen
抛出异常:- 如果
inner_gen
产生了StopIteration
异常,
则gen
会继续执行yield from
之后的语句 - 如果对
inner_gen
产生了非StopIteration
异常,则传导至gen
中,
导致gen
在执行yield from
的时候抛出异常
- 如果
gen
抛出异常:- 如果
gen
产生了除GeneratorExit
以外的异常,则该异常直接 throw 到inner_gen
中 - 如果
gen
产生了GeneratorExit
异常,或者gen
的.close()
方法被调用,
则inner_gen
的.close()
方法被调用。
- 如果
gen
中yield from
表达式求职结果是inner_gen
迭代结束时抛出的StopIteration
异常的第一个参数inner_gen
中的return xxx
语句实际上会抛出一个StopIteration(xxx)
异常,
所以inner_gen
中的return
值会成为gen
中的yield from
表达式的返回值。