yield
为了理解什么是 yield
,你必须理解什么是生成器。在理解生成器之前,让我们先知道什么是迭代。
可迭代对象
当你建立了一个列表,你可以逐项地读取这个列表,这叫做一个可迭代对象:
>>> mylist = [1, 2, 3]
>>> for i in mylist :
... print(i)
1
2
3
mylist
是一个可迭代的对象。当你使用一个列表生成式来建立一个列表的时候,就建立了一个可迭代的对象:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist :
... print(i)
0
1
4
使用迭代器会把所有的值都存储到了内存中,如果你有大量数据的话这个方式并不是你想要的。
生成器
生成器是可以迭代的,但是你 只可以读取它一次 ,因为它并不把所有的值放在内存中,它是实时地生成数据
def get_yield(limit):
for i in range(limit):
yield i
t = get_yield(5)
print(t)
# t输出为 <generator object get_yield at 0x000001C69833CEB8>
for num in t:
print(num)
# 下面这一段不会输出
for num in t:
print(num)
yield和return很相似,都起到返回值的作用,但是yield所在的函数返回的是一个生成器,return则直接将某个变量返回,并退出函数。要知道,上面的get_yield函数并不是一被调用就马上执行,可以观看下段代码来更好地了解这一点。
import time
def oneByone(limit):
for i in range(limit):
print('oneByone里面i的值是:', i)
yield i
t = oneByone(5)
for j in range(5):
i = next(t)
print(
print('main函数里面i的值是:' , i)
time.sleep(1)
print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
输出结果:
oneByone里面i的值是: 0
main函数里面i的值是: 0
2018-07-30 12:40:54
oneByone里面i的值是: 1
main函数里面i的值是: 1
2018-07-30 12:40:55
oneByone里面i的值是: 2
main函数里面i的值是: 2
2018-07-30 12:40:56
oneByone里面i的值是: 3
main函数里面i的值是: 3
2018-07-30 12:40:57
oneByone里面i的值是: 4
main函数里面i的值是: 4
2018-07-30 12:40:58
具体理解可以参见高级用法部分的next方法
高级用法:
1.控制生成器的穷尽(摘自该文章)
>>> class Bank(): # let's create a bank, building ATMs
... crisis = False
... def create_atm(self) :
... while not self.crisis :
... yield "$100"
>>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(next(corner_street_atm))
$100
>>> print(next(corner_street_atm))
$100
>>> print([next(corner_street_atm) for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # crisis is coming, no more money!
>>> print(next(corner_street_atm))
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs
>>> print(next(wall_street_atm))
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty
>>> print(next(corner_street_atm))
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business
>>> for cash in brand_new_atm :
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
这里面的逻辑是这么一回事:首先实现了一个Bank类,然后定义了类的所有实例共享的成员变量crisis,当crisis为False时,生成器create_atm()可迭代,反之不行。创建一个hsbc实例,首先创建corner_street_atm这个生成器,可以一直使用next(corner_street_atm)来迭代,但是一旦将crisis设为True,且再次调用next(corner_street_atm)时,该生成器就不可迭代了
(注意,将crisis设置为True之后,一定要再次调用生成器该生成器才会不可迭代
这里的逻辑其实很好懂,为什么需要再次调用才会不可迭代呢?因为上面的函数并不会一次性执行完,而是使用next则执行一次,所以当你将crisis设置为True时,如果不使用next方法,则一直不会跳出while循环)。
2.send和next方法
python生成器有两个主要方法,一个是send一个是next。
在每段代码中,第一个next调用,相当于启动生成器,会从生成器函数的第一行代码开始执行,直到第一次执行完yield语句(第4行)后,跳出生成器函数。然后第二个next调用,进入生成器函数后,从yield语句的下一句语句(第5行)开始执行,然后重新运行到yield语句,执行后,跳出生成器函数后面再次调用next,依此类推。即执行一次next则调用一次生成器函数,直至抛出不可迭代的错误。
1 def consumer():
2 r = 0
3 for i in xrange(3):
4 yield r
5 r = '200 OK'+ str(i)
6
7 c = consumer()
8 n1 = c.next()
9 n2 = c.next()
10 n3 = c.next()
(该段代码摘自该文章)
send方法和next方法其实很相似,唯一的区别在于send方法可以传入值,而next方法不能传值,可以这样说,next(c) = c.send(None)
第一次调用时不能使用send发送一个非None的值,否则会出错的,因为没有Python yield语句来接收这个值。
def consumer():
r = ''
while True:
n = yield r # A
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None) # B
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n) # C
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
在该段代码里面,首先是创建了生成器c,然后将c传入produce里面。下面开始讲解produce和consume函数的交互:
首先执行c.send(None),然后进入生成器函数里面,执行到A处(注意,此处没有执行赋值操作,遇见yield就跳回B了),然后从B的末尾继续执行,在C处执行r = c.send(1),然后又跳回到生成器函数的A处,那么send是怎么赋值的呢???这里是将(yield r)当做一个整体,然后将该值赋给n,也就是n=1了,继续执行A后面的语句,直至再次到A处,重复前面的步骤至循环结束。
另外,C处的语句r都会接受到生成器函数返回的r值。
3.yield from
https://www.cnblogs.com/cdma/p/6839640.html
https://blog.csdn.net/chenbin520/article/details/78111399?locationNum=7&fps=1