Python yield 总结

本文深入探讨了Python中yield的关键概念,包括迭代器、生成器及其工作原理。通过具体实例介绍了如何使用yield创建生成器,以及如何利用next()和send()方法控制生成器的执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对python的yield一直没搞明白,今天心血来潮,看了一下书和网上的博客 。

FIRST

迭代器(Iterator)

为了理解yield是什么,首先要明白生成器(generator)是什么,在讲生成器之前先说说迭代器(iterator),当创建一个列表(list)时,你可以逐个的读取每一项,这就叫做迭代(iteration)。

 mylist = [1, 2, 3]
 for i in mylist :
     print(i)
 1
 2
 3

Mylist就是一个迭代器,不管是使用复杂的表达式列表,还是直接创建一个列表,都是可迭代的对象。

你可以使用“for··· in ···”来操作可迭代对象,如:list,string,files,这些迭代对象非常方便我们使用,因为你可以按照你的意愿进行重复的读取。但是你不得不预先存储所有的元素在内存中,那些对象里有很多元素时,并不是每一项都对你有用。

生成器(Generators)

生成器同样是可迭代对象,但是你只能读取一次,因为它并没有把所有值存放内存中,它动态的生成值:

mygenerator = (x*x for x in range(3))
   for i in mygenerator :
       print(i)
   0
   1
   4

使用()和[]结果是一样的,但是,第二次执行“ for in mygenerator”不会有任何结果返回,因为它只能使用一次。首先计算0,然后计算1,之后计算4,依次类推。你可以试一下,真的只能运行一次。

Yield

Yield是关键字, 用起来像return,yield在告诉程序,要求函数返回一个生成器。

def createGenerator() :
       mylist = range(3)
       for i in mylist :
           yield i*i
   mygenerator = createGenerator() # create a generator
   print(mygenerator) # mygenerator is an object!
   <generator object createGenerator at 0xb7555c34>
   for i in mygenerator:
       print(i)
   0
   1
   4

这个示例本身没什么意义,但是它很清晰地说明函数将返回一组仅能读一次的值,要想掌握yield,首先必须理解的是:当你调用生成器函数的时候,如上例中的createGenerator(),程序并不会执行函数体内的代码,它仅仅只是返回生成器对象,这种方式颇为微妙。函数体内的代码只有直到每次循环迭代(for)生成器的时候才会运行。

函数第一次运行时,它会从函数开始处直到碰到yield时,就返回循环的第一个值,然后,交互的运行、返回,直到没有值返回为止。如果函数在运行但是并没有遇到yield,就认为该生成器是空,原因可能是循环终止,或者没有满足任何”if/else”。

AND THEN

接下来我引用另一篇博客关于yield的应用的例子

def h():
    print 'To be brave'
    yield 5

h()

这样执行是没有输出的,因为如前面所言,h()只是调用生成函数,并不会执行函数体内的代码,仅是返回生成体对象。

那怎么执行内部代码呢?继续看

def h():
    print 'Wen Chuan'
    yield 5
    print 'Fighting!'

c = h()
c.next()

我们通过next()语句让它执行。next()语句将恢复Generator的执行,并直到下一个yield表达式处。c.next()调用后,h()开始执行,直到遇到yield 5,因此输出结果:

Wen Chuan

当我们再次调用c.next()时,会继续执行,直到找到下一个yield表达式。由于后面没有yield了,因此会拋出异常:

Wen Chuan
Fighting!
Traceback (most recent call last):
File "/home/evergreen/Codes/yidld.py", line 11, in <module>
c.next()
StopIteration

了解了next()如何让包含yield的函数执行后,我们再来看另外一个非常重要的函数send(msg)。其实next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此,我们可以看做c.next() 和 c.send(None) 作用是一样的。

def h():
print 'Wen Chuan',
m = yield 5  # Fighting!
print m
d = yield 12
print 'We are together!'
c = h()
c.next()  #相当于c.send(None)
c.send('Fighting!')  #(yield 5)表达式被赋予了'Fighting!'

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

我一开始看上面的提醒有点懵逼,为什么一开始不能用send(something),something不等于None,后来我在”python基础教程”中找到了原因:

使用send方法(而不是next方法)只有在生成器挂起之后才有意义,也就是说在yield函数第一次被执行之后,如果在此之前需要给生成器提供更多信息,那么只需使用生成器函数的参数。

如果真想对刚刚启动的生成器使用send方法,那么可以将None作为其参数进行调用。

另外,为什么print m的内容是fighting呢?

在内部挂起生成器,yield作为表达式使用,换句话说,当生成器重新运行的时候,yield方法返回一个值,也就是外部通过send方法发送的值。如果是next方法,那么yield方法返回0。

继续那篇文章的例子

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

def h():
    print 'Wen Chuan',
    m = yield 5  # Fighting!
    print m
    d = yield 12
    print 'We are together!'

c = h()
m = c.next()  #m 获取了yield 5 的参数值 5
d = c.send('Fighting!')  #d 获取了yield 12 的参数值12
print 'We will never forget the date', m, '.', d

输出结果:

Wen Chuan Fighting!
We will never forget the date 5 . 12

中断Generator是一个非常灵活的技巧,可以通过throw抛出一个GeneratorExit异常来终止Generator。Close()方法作用是一样的,其实内部它是调用了throw(GeneratorExit)的。我们看:

 def close(self):
     try:
         self.throw(GeneratorExit)
     except (GeneratorExit, StopIteration):
         pass
     else:
         raise RuntimeError("generator ignored GeneratorExit")
#Other exceptions are not caught

因此,当我们调用了close()方法后,再调用next()或是send(msg)的话会抛出一个异常:

Traceback (most recent call last):
File "/home/evergreen/Codes/yidld.py", line 14, in <module>
d = c.send('Fighting!')  #d 获取了yield 12 的参数值12
StopIteration

参考文章

1.http://blog.jobbole.com/28506/
2.http://www.jb51.net/article/15717.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值