yield详解

1、包含yield的函数

假如你看到某个函数包含了yield,这意味着这个函数已经是一个Generator,它的执行会和其他普通的函数有很多不同。比如下面的简单的函数:

可以看到,调用h()之后,print 语句并没有执行!这就是yield,那么,如何让print 语句执行呢?这就是后面要讨论的问题,通过后面的讨论和学习,就会明白yield的工作原理了。

In [52]: def h():   
   ....:         print 'To be brave'    
   ....:         yield 5    
   ....:   
In [53]: h()   
Out[53]: <generator object h at 0x2a6f370>
In [54]: def h():   
        print 'To be brave'
In [55]:
In [55]: h()   
To be brave

2、yield是一个表达式

m = yield 5

表达式(yield 5)的返回值将赋值给m,所以,认为 m = 5 是错误的。那么如何获取(yield 5)的返回值呢?需要用到后面要介绍的send(msg)方法。

4、send(msg) 与 next()

其实next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此,我们可以看做    
c.next() 和 c.send(None) 作用是一样的。

In [78]: def h():   
         print 'Wen Chuan',    
         m = yield 5  # Fighting!    
         print'm is %s:' % m    
         d = yield 12    
         print 'We are together!'    
   ....:    
In [79]: c=h()
In [80]: c.next()   
Wen ChuanOut[80]: 5
In [81]: c.next()   
m is None:    
Out[81]: 12
In [82]: c=h()
In [83]: c.next()     #相当于c.send(None)    
Wen ChuanOut[83]: 5
In [84]: c.send('fighting')    #(yield 5)表达式被赋予了'Fighting!'    
m is fighting:    
Out[84]: 12

需要提醒的是,第一次调用时,请使用next()语句或是send(None),不能使用send发送一个非None的值,否则会出错的,因为没有yield语句来接收这个值。    

5. send(msg) 与 next()的返回值

send(msg) 和 next()是有返回值的,它们的返回值很特殊,返回的是下一个yield表达式的参数。比如yield 5,则返回 5 。到这里,是不是明白了一些什么东西?本文第一个例子中,通过for i in alist 遍历 Generator,其实是每次都调用了alist.Next(),而每次alist.Next()的返回值正是yield的参数,即我们开始认为被压进去的东东。我们再延续上面的例子:

In [91]: def h():   
         print 'Wen Chuan',    
         m = yield 5  # Fighting!    
         print m    
         d = yield 12    
         print 'We are together!'    
   ....:    
In [92]: c=h()
In [93]: m=c.next()   #m 获取了yield 5 的参数值 5    
Wen Chuan
In [94]: d=c.send('fighting')   #d 获取了yield 12 的参数值12    
fighting  --- 表达式(yield 12)的值m为fighting    
In [96]: print 'we will never forget the date',m,'.',d    
we will never forget the date 5 . 12

 

《提高你的Python: 解释‘yield’和‘Generators(生成器)’》


yield就是专门给生成器用的return(加上点小魔法)。

下面是一个简单的生成器函数:

In [3]: def simple_generator_function():   
   ...:     yield 1    
   ...:     yield 2    
   ...:     yield 3    
   ...: 
这里有两个简单的方法来使用它:
In [4]: for value in simple_generator_function():   
   ...:     print(value)    
   ...:     
1    
2    
3    
In [5]: our_generator = simple_generator_function()
In [6]: next(our_generator)   
Out[6]: 1
In [7]: next(our_generator)   
Out[7]: 2
In [8]: next(our_generator)   
Out[8]: 3    

 
In [1]: def is_prime(number):   
   ...:     if number > 1:    
   ...:         if number == 2:    
   ...:             return True    
   ...:         if number % 2 == 0:    
   ...:             return False    
   ...:         for current in range(3, int(math.sqrt(number) + 1), 2):    
   ...:             if number % current == 0:     
   ...:                 return False    
   ...:         return True    
   ...:     return False    
   ...:
In [2]: def get_primes(number):    
   ...:     while True:     
   ...:         if is_prime(number):     
   ...:             yield number     
   ...:         number += 1    
   ...:

 

yield执行流程分析

调用get_primes

In [30]: def solve_number_10():   
   ....:     # She *is* working on Project Euler #10, I knew it!    
   ....:     total = 2    
   ....:     for next_prime in get_primes(3):    
   ....:         if next_prime < 2000000:    
   ....:             total += next_prime    
   ....:         else:    
   ....:             print(total)    
   ....:             return    
   ....:

我们来看一下solve_number_10的for循环中对get_primes的调用,观察一下前几个元素是如何创建的有助于我们的理解。当for循环从get_primes请求第一个值时,我们进入get_primes,这时与进入普通函数没有区别。

  1. 进入第三行的while循环
  2. 停在if条件判断(3是素数)
  3. 通过yield将3和执行控制权返回给solve_number_10

接下来,回到insolve_number_10:

  1. for循环得到返回值3
  2. for循环将其赋给next_prime
  3. total加上next_prime
  4. for循环从get_primes请求下一个值

这次,进入get_primes时并没有从开头执行,我们从第5行继续执行,也就是上次离开的地方。

In [2]: def get_primes(number):    
   ...:     while True:     
   ...:         if is_prime(number):     
   ...:             yield number     
   ...:         number += 1     # <<<<<<<<<<    
   ...:

最关键的是,number还保持我们上次调用yield时的值(例如3)。记住,yield会将值传给next()的调用方,同时还会保存生成器函数的“状态”。接下来,number加到4,回到while循环的开始处,然后继续增加直到得到下一个素数(5)。我们再一次把number的值通过yield返回给solve_number_10的for循环。这个周期会一直执行,直到for循环结束(得到的素数大于2,000,000)。

 

转载于:https://blog.51cto.com/f1yinsky/1916516