Python迭代器与生成器
迭代器(Iterator)
定义
- 可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator,也就是说迭代器具有__next__()方法
- 个人理解只要能使用next函数就是迭代器,所以生成器也是迭代器(其中的一种
比如正方形也是矩形)
特点
- 迭代器是消耗的,用过之后无法再使用(下面的例子可以看出第二遍循环时没有打印了)
emp = iter([1,2])
for i in emp:
print(i)
for i in emp:
print(i)
1
2
- 迭代器是没法后退的,只能通过next,不断的取下一个值(所以列表,元组等不是迭代器,只是可迭代对象)
- 迭代器有两个基本的方法:iter() 和 next()
- iter():用来生成迭代器(由下面代码可以看到,iter接受的是可迭代的对象,所以传入迭代器也是可以的)
- next():取迭代器的下一个值(当没有下一个元素时会抛StopIteration异常)
- 惰性求值
# iter()
from collections.abc import Iterator
test00 = [1,2]
test01 = iter([1,2,3])
test02 = (x for x in range(2))
test03 = iter(test02)
test04 = iter(test01)
print(isinstance(test00,Iterator))
print(isinstance(test01,Iterator))
print(isinstance(test02,Iterator))
print(isinstance(test03,Iterator))
print(isinstance(test04,Iterator))
False
True
True
True
True
from collections.abc import Iterator
test00 = [1,2]
test01 = iter([1,2,3])
test02 = (x for x in range(2))
test03 = iter(test02)
test04 = iter(test01)
# 列表作为迭代器对象只有__iter__,而升级为迭代器后就有__next__方法了
print(dir(test00))
print(dir(test01))
# 迭代器对象本身中就含有__iter__和__next__
print(dir(test02))
# 迭代器对象升级为列表后,id(内存地址)是不一样的
print(id(test00))
print(id(test01))
# 迭代器再使用iter()方法,id(内存地址)是不变的
print(id(test02))
print(id(test03))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__']
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
4398346312
4398304560
4397514688
4397514688
# next()
test = iter([1,2])
print(next(test))
print(next(test))
print(next(test))
1
2
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-10-4bb69c4a1d3d> in <module>()
3 print(next(test))
4 print(next(test))
----> 5 print(next(test))
StopIteration:
注意
- 生成器都是Iterator对象,但list、dict、str虽然是Iterable(可迭代对象),却不是Iterator(迭代器)
- 迭代器都是可迭代的,因为实现了__iter___()
- Python3的for循环本质上就是通过不断调用next()函数实现的,把list、dict、str等Iterable变成Iterator可以使用iter()函数
- 把一个类作为一个迭代器使用需要在类中实现两个方法 iter() 与 next()
*1、有__next__()方法,返回容器的下一个元素或抛出StopIteration异常
*2、有__iter__()方法,返回迭代器本身
Tips
- 有限序列中生成无限序列
from itertools import cycle
test01 = [1,2]
test02 = cycle(test01)
print(next(test02))
print(next(test02))
print(next(test02))
print(next(test02))
print(next(test02))
- 无限序列中生成有限序列 islice(iterable, [start,] stop [, step])
iterable 是可迭代对象,start 是开始索引,stop 是结束索引,step 是步长,start 和 step 可选
from itertools import cycle, islice
test01 = [1,2]
test02 = cycle(test01)
test03 = islice(test02,0,3)
for i in test03:
print(i)
可迭代对象(Iterable)
定义
- 这些可以直接作用于for 循环的对象统称为可迭代对象:Iterable
- 个人理解:这里的迭代对象可以理解为可遍历对象,可迭代对象有__iter__()方法
类型
- 集合数据类型(collection):如list,tuple,dict,set,str等
- 生成器(generator):包括生成器和带yield的generator function
from collections.abc import Iterable
col_exp = [1,2]
gen_exp = (x for x in range(2))
print(isinstance(col_exp, Iterable))
print(isinstance(gen_exp, Iterable))
注意
- 可迭代对象不一定是迭代器,比如集合数据类型就都不是迭代器
- 可迭代对象实现了__iter__()
from collections.abc import Iterable
class IterTest():
def __iter__(self):
pass
test01 = IterTest()
print(isinstance(test01, Iterable))
生成器(generator)
定义
生成器是一种特殊的迭代器(所以任何生成器都是迭代器)
Python 生成器与它的 send,throw,close 方法这篇文章采用了5中验证方式来说明生成器就是迭代器。但生成器并不完全等于迭代器,因为生成器相当于迭代器的子类,throw,close这些方法迭代器都没有。
类型
生成器有两种类型:
- 生成器函数:yield
- 生成器表达式: () eg:(x for x in range(5))
生成器表达式与列表解析式类似,, 列表解析式: [x for x in range(5)]
# yiled
from itertools import islice
def YieldTest(n):
while True:
yield n
n += 1
a = YieldTest(5) # 生成器被实例化
for i in islice(a,0,3):
print(i)
send,throw,close
因为生成器也是迭代器,所以next方法也是有的,用法也和迭代器一样,
但生成器也有它一些独特的方法,主要是生成器函数中使用
- send
- send方法相当于是与生成器通信的桥梁
- send方法可以看作是传值+next(),所以就有传值,恢复执行,以及返回值这些操作
- send方法入果在生成器一启动就开始使用时需要传入None,send(None)。
def sendtest():
sendmsg = yield '这是yield'
print(sendmsg)
test = SendTest()
print(test.send(None))
try:
test.send('这是send') # 生成器运行完会抛StopIteration
except StopIteration:
print('生成器运行结束')
- throw
- 这里throw用于抛异常,有点java的感觉
- 抛的异常在生成器暂停的地方抛出,不进行捕获的话,会传给调用方
- 对于已经通过抛出异常而退出的生成器再使用 next(g) 会持续抛出 StopIteration 异常
- 如果生成器退出时还没有 yield 新值,则会抛出 StopIteration 异常,
因为throw是要返回值的,相当于调用了next,没有yield自然会抛StopIteration 异常
def throwtest():
yield 1
yield 2
yield 3
test = throwtest()
print(next(test))
# 这里throwtest没有对异常处理,所以throwtest将异常传给了调用方,这时调用方也不会收到任何值
print(test.throw(ZeroDivisionError))
def throwtest():
try:
yield 1
except ZeroDivisionError:
pass
yield 2
yield 3
test = throwtest()
print(next(test))
# 这里throwtest对异常处理,也可以看见异常是发生在yield 1,也证明了抛出的异常在在生成器暂停的地方抛出,并且返回了下一个yield的值
print(test.throw(ZeroDivisionError))
print(next(test))
def throwtest():
yield 1
yield 2
yield 3
test = throwtest()
print(next(test))
try:
print(test.throw(ZeroDivisionError))
except ZeroDivisionError:
print('捕获了异常')
# 对于已经通过抛出异常而退出的生成器再使用 next(g) 会持续抛出 StopIteration 异常
print(next(test))
def throwtest():
try:
yield 1
except ZeroDivisionError:
print('捕获异常')
# yield 2
test = throwtest()
print(next(test))
# 因为需要返回值而已经没有yield所以抛出了StopIteration,注释掉yield 2就不会抛异常了
print(test.throw(ZeroDivisionError))
- close(在生成器函数暂停的地方抛出一个 GeneratorExit 异常)
- 这并不等价于generator.throw(GeneratorExit)
- 如果生成器抛出StopIteration异常(不管是由于正常退出还是因为该生成器已经关闭),或者抛出GeneratorExit异常(不捕获该异常即可),close方法不传递该异常,直接返回到调用方。而生成器抛出的其他异常会传递给调用方。见下面第二个例子
- GeneratorExit异常的产生意味着生成器对象的生命周期已经结束,因此生成器方法后续语句中不能再有yield,否则会产生RuntimeError。(而 throw方法是期待一个yield返回值的,如果没有,则会抛出StopIteration异常。)
- 对于已经正常退出或者因为异常退出的生成器对象,close 方法不会进行任何操作
- GeneratorExit异常只有在生成器对象被激活后,才有可能产生。
这里激活指生成器使用next(或send)等方式
def closetest():
yield 1
yield 2
test = closetest()
print(next(test))
print(test.close())
# 这里可以看到,close虽然抛了GeneratorExit异常,在生成器函数中也没有对其进行捕获,但异常也没有传回给调用方,这和throw是不一样的
# 然后close是没有返回值的
1
None
def closetest2():
try:
yield 1
except GeneratorExit:
print('捕获异常')
print('生成器结束')
test = closetest2()
print(next(test))
test.close() # 这里调用close不传递StopIteration异常
1
捕获异常
生成器结束
def closetest3():
try:
yield 1
except GeneratorExit:
print('捕获异常')
yield 2
test = closetest3()
print(next(test))
test.close()
1
捕获异常
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-13-279ecc9274b8> in <module>()
7 test = closetest3()
8 print(next(test))
----> 9 test.close()
RuntimeError: generator ignored GeneratorExit
yield from
这主要是在协程中用到,起到一个通信桥梁的作用
后面做并发笔记,思考协程的时候在具体写吧,可以参考下面的博客!讲的和我看的一个python高级编程中yield from的视频差不多,类似笔记挺好的
注意
生成器原理是利用了堆栈帧是分配在堆内存中 生成器原理