本文首发于知乎
yield生成器在python中使用普遍,更是python中协程的实现原理,有必要深刻掌握。python
本文分为以下几个部分编程
下面是一个最简单的yield使用的例子ruby
def myfun(total):
for i in range(total):
yield i
复制代码
定义了myfun
函数,调用时好比myfun(4)
获得的是一个生成器,生成器有3种调用方式bash
1.用next调用app
>>> 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)
复制代码
运行结果为ui
0
1
2
3
复制代码
生成器是可迭代对象,能够用循环调用。循环调用就是最大限度地调用next
,并返回每次next
运行结果spa
这就是yield的最基本使用,下面来应用一下code
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空至关于一个中断器,循环运行到这里会中断,用于辅助其余程序的执行。也能够理解成返回值是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
的用法
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
,首先明确一点,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打印出来,即第二个1a.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
只是起到终止函数的做用,先看下面这个函数
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编程
专栏目录:目录
版本说明:软件及包版本说明