本文首发于知乎
yield生成器在python中使用广泛,更是python中协程的实现原理,有必要深入掌握。
本文分为如下几个部分
- 简单yield的使用
- yield空
- yield from
- send与yield赋值
- return yield
简单yield的使用
下面是一个最简单的yield使用的例子
def myfun(total):
for i in range(total):
yield i
复制代码
定义了myfun
函数,调用时比如myfun(4)
得到的是一个生成器,生成器有3种调用方式
1.用next调用
>>> m = myfun(4)
>>> next(m)
0
>>> next(m)
1
>>> next(m)
2
>>> next(m)
3
>>> next(m)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
复制代码
每次调用一次next
,函数都会执行到yield
处停止,等待下一次next
,next
次数超出限制就会抛出异常
2.循环调用
for i in myfun(4):
print(i)
复制代码
运行结果为
0
1
2
3
复制代码
生成器是可迭代对象,可以用循环调用。循环调用就是最大限度地调用next
,并返回每次next
运行结果
这就是yield的最基本使用,下面来应用一下
python内置模块itertools中实现了一些小的生成器,我们这里拿count
举例子,要实现下面这样的效果
>>> from itertools import count
>>> c = count(start = 5, step = 2)
>>> next(c)
5
>>> next(c)
7
>>> next(c)
9
>>> next(c)
11
......
复制代码
实现如下
def count(start, step = 1):
n = 0
while True:
yield start + n
n += step
复制代码
3.循环中调用next
下面循环中调用next
,用StopIteration
捕获异常并退出循环
>>> m = myfun(4)
>>> while True:
... try:
... print(next(m))
... except StopIteration:
... break
...
0
1
2
3
复制代码
yield空
yield空相当于一个中断器,循环运行到这里会中断,用于辅助其他程序的执行。也可以理解成返回值是None
,我们来看下面这个例子
>>> def myfun(total):
... for i in range(total):
... print(i + 1)
... yield
...
>>> a = myfun(3)
>>> next(a)
1
>>> next(a)
2
>>> next(a)
3
>>> next(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> a = myfun(3)
>>> for i in a:
... print(i)
...
1
None
2
None
3
None
复制代码
yield from
通过下面一个例子来展示yield from
的用法
def myfun(total):
for i in range(total):
yield i
yield from ['a', 'b', 'c']
复制代码
用next
调用结果如下
>>> m = myfun(3)
>>> next(m)
0
>>> next(m)
1
>>> next(m)
2
>>> next(m)
'a'
>>> next(m)
'b'
>>> next(m)
'c'
>>> next(m)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
复制代码
上面的函数相当于
def myfun(total):
for i in range(total):
yield i
for i in ['a', 'b', 'c']:
yield i
复制代码
下面我们也做一个小练习,实现itertools模块中的cycle
,效果如下
>>> from itertools import cycle
>>> a = cycle('abc')
>>> next(a)
'a'
>>> next(a)
'b'
>>> next(a)
'c'
>>> next(a)
'a'
>>> next(a)
'b'
>>> next(a)
'c'
>>> next(a)
'a'
复制代码
实现如下
def cycle(p):
yield from p
yield from cycle(p)
复制代码
send与yield赋值
先讲send
,首先明确一点,next
相当于send(None)
,还是看下面最简单的例子
>>> def myfun(total):
... for i in range(total):
... yield i
...
>>> a = myfun(3)
>>> a.send(None)
0
>>> a.send(None)
1
>>> a.send(None)
2
>>> a.send(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
复制代码
如果send()
参数不是None
呢?send()
表示向这个生成器中传入东西,有传入就得有变量接着,于是引出了yield
赋值,来看下面一个例子
def myfun(total):
for i in range(total):
r = yield i
print(r)
复制代码
运行如下
>>> a = myfun(3)
>>> a.send(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
>>> a.send(None)
0
>>> a.send(1)
1
1
>>> a.send(1)
1
2
>>> a.send(1)
1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
复制代码
上面运行结果显示
1.第一次send
参数必须是None
,用于初始化生成器
- 整个程序运行细节非常重要,详细拆解如下
- 第一步
a.send(None)
,是执行第一次循环,运行到yield i
。注意:没有运行r = yield
赋值这一步,这也是第一次只能传入None
的原因 - 第二步
a.send(1)
,先r = yield
赋值,将send
进去的1赋值给了r
;然后print(r)
即第一个打印出来的1;之后进入第二次循环,运行到yield i
没有赋值,把yield
的结果1打印出来,即第二个1 - 最后一次
a.send(1)
先对r
赋值,再print(r)
;然后就退出了循环,没有什么可以yield
的了,于是抛出异常
有了send
这样的机制,我们就可以实现函数之间的来回切换执行,这是协程的基础。
廖雪峰老师网站上用这一特性完成了一个类似生产者消费者模式的例子,读者可以看看根据上面的知识能不能看懂这个例子
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
复制代码
运行结果如下
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
复制代码
上面例子看起来复杂高大上,但是如果从实现上述功能上来看,实际上是走了弯路,下面的代码可以实现和上面一样的功能,读者可以细细品味
def consumer(n):
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
return '200 OK'
def produce():
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = consumer(n)
print('[PRODUCER] Consumer return: %s' % r)
produce()
复制代码
return yield
有时会看到return yield
的用法,其实return
只是起到终止函数的作用,先看下面这个函数
def myfun(total):
yield from range(total)
复制代码
>>> a = myfun(4)
>>> a
<generator object myfun at 0x000001B61CCB9CA8>
>>> for i in a:
... print(i)
...
0
1
2
3
复制代码
这样a
就是个生成器。如果用return
也一样
# 不加括号不合规定
>>> def myfun(total):
... return yield from range(total)
File "<stdin>", line 2
return yield from range(total)
^
SyntaxError: invalid syntax
>>> def myfun(total):
... return (yield from range(total))
...
>>> a = myfun(4)
>>> for i in a:
... print(i)
...
0
1
2
3
复制代码
不过下面两个不一样
def myfun1(total):
return (yield from range(total))
yield 1 # 这个1在return后面,不会再执行
def myfun2(total):
yield from range(total)
yield 1
复制代码
看下面运行结果
>>> a = myfun1(3)
>>> for i in a:
... print(i)
...
0
1
2
>>> a = myfun2(3)
>>> for i in a:
... print(i)
...
0
1
2
1
复制代码
欢迎关注我的知乎专栏
专栏主页:python编程
专栏目录:目录
版本说明:软件及包版本说明