python 迭代器与生成器
在之前的文章for 循环的本质
中已经提及了迭代器协议。
而生成器又是什么呢?和迭代器又有什么关系呢?本文将为你解惑。
先看如下代码
def g():
for i in range(3):
yield i
a = g()
上文的代码中,a 就是一个生成器对象。
该对象可以使用如下代码进行迭代
for i in a:
print(i)
之前文章说过,使用for循环迭代的对象需要实现 __iter__
方法,并且这个方法需要返回一个
迭代器对象(该对象需要实现 __iter__
和 __next__
方法)。
而如果在__iter__
方法中使用yield关键字,将自动返回一个生成器对象。
生成器对象会自动包含上述两方法。
从这种角度来说生成器提供了一种快速实现迭代器协议的方式。
生成器也可以视为一种特殊的迭代器。
定义: 包含yield语句的函数成为生成器函数,当然也有可能是异步生成器函数。
而生成器函数返回的对象就是一个生成器对象。
生成器还能通过另外一种生成器语句定义,如下:
a = (x for x in [1,2,3])
a
就是一个生成器对象。
定义:与推导式相同的语法,但是使用圆括号定义的表达式返回的对象就是生成器对象。
右侧的表达式也称为生成器表达式
那么生成器与迭代器的不同点在哪呢。
print(dir(x for x in range(3)))
['__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']
我们可以发现,相比于迭代器,生成器不仅包含__iter__
, __next__
方法,
还包含close
, send
, throw
等一些重要的方法。
那这三者又是做什么的呢?
我们再来看以下的代码。
def g():
try:
for i in range(3):
try:
value = yield i
print(value)
except Exception as e:
print(f'except {e}')
finally:
print('close')
a = g()
c0 = a.send(None)
c1 = a.send('send_1')
c2 = a.send('send_2')
print(c0, c1, c2)
a.close()
我们知道,一个迭代器,可以使用 next 方法进行迭代。而生成器不止可以使用 next 方法,
还能使用其提供的 send 方法控制生成器函数。
第一个 send(None) 是必须的用于启动生成器函数,执行到 yield i
这一步。
此时函数返回值赋值给 c0 。因为此时 yield 语句左侧还未执行,
无法赋值所以必须是 send(None). 而后 send(‘send 1’) 调用。
send 函数中的参数 send_1 被赋值给 value. 生成器函数继续执行。
一直到下一次执行 yield 语句。
而 close 函数执行时,会自动执行生成器函数中的 finally 部分代码。
throw 函数 会在生成器函数暂停位置引发 throw 参数中传递的异常。
如果有捕获该异常则正常执行,没有则关闭生成器,并传播给调用者。
总之,这三个函数是用于控制生成器函数的执行。
最后我们做个总结:
- 1.生成器是特殊的迭代器
- 2.生成器可以由生成器函数产生
- 3.生成器用于控制生成器函数的执行
练习:想想在什么场景下可以使用生成器优化代码呢。留下你的代码在评论吧。
当然也欢迎扫一扫关注我的微信公众号。