神奇的yield表达式

神奇的yield表达式

相信深入学习python的同学一定晓得gevent,一个基于协程的高级并发库,尤其是在受限于网络或是IO的情况下,更能发挥其惊人的威力,由于IO比较耗时,经常使程序处于等待状态,gevent通过协程的上下文切换来避免等待,保证总有协程在运行。听起来很炫酷吧,而gevent中的协程上下文切换正是通过yield实现的。

yield表达式唯一能做的就是构造生成器函数,也就是必须用在函数体内部,而不能作为全局表达式使用,一言不合咱上例子:

yield “wrong way!”

输出:
File "C:\Users\Administrator\Desktop\testgen.py", line 3
    yield 111
SyntaxError: 'yield' outside function

正确例子:
def test_gen():
 print "this is a generator function"
 yield "hi guys!"

当得到一个生成器函数该如何使用呢,既然说yield神奇,那肯定不能像普通函数那样调用了。在通常的小程序中最常用的就是for循环调用了,当调用生成器函数时,它会返回一个迭代器(iterator)。示例:

def test_gen():
    for num in range(5):
        yield num

for num in test_gen():
    print num

输出如下:
0
1
2
3
4

当调用生成器函数时,会返回yield表达式后边的值,也就是示例中的num变量。通常这样的用法跟迭代器比较类似,最大的好处应该就是节省内存,当碰到巨型数据结构时可以尝试将其转化为一个生成器函数或是迭代器以此来解决内存不足的窘境。当然生成器函数不只是个迭代器而已,下边开始讲下其正真的神奇之处。

生成器函数有4个方法,用来控制它的执行过程,尤其是send和next是常用的两个方法。

1、next()方法

#generator.next()
def test_next():
    print "start next test!"
    r = yield 666
    if r is None:
        print "r is None"
    for i in range(1,3):
        print "pause at %d yield" % i
        yield i

g = test_next()
first = g.next()
print "first next return %s" % first
print "start second call"
second = g.next()
print "second next return %s" % second

输出:
start next test!
first next return 666
start second call
r is None
pause at 1 yield
second next return 1

首次调用next时,会在第一个yield处暂停,并返回yield后的值,此时first=666;在第二次调用next时,会将当前暂停的整个yield表达式,即”yield 666″赋值为None,并继续向下执行函数,直到下一个yield处,再次返回第二个yield后边的值。也就是第二次调用next做了两个操作:给当前yield表达式赋值为None和返回下个yield后边的值。如此往复执行,直到没有返回值,此时继续调用会抛出StopIteration异常,如下所示:

def test_gen():
    for num in range(2):
        yield num
g = test_gen()
g.next()
g.next()
g.next()

输出:
Traceback (most recent call last):
  File "C:\Users\Administrator\Desktop\testgen.py", line 7, in <module>
    g.next()
StopIteration

而通过for循环调用生成器函数则不会有该异常,因为for循环内部已经处理了该异常,有兴趣可以看下for循环的源码实现。如果通过next方式调用最好加上异常处理,放置程序意外终止。

2、send()方法

#generator.send(value)
def test_send():
    print "start send test!"
    r = yield "hello world"
    print "recieved value from send: %s " % r
    for i in range(1,3):
        print "paused at %d yield" % i
        yield i

g = test_send()
h = g.send(None)
print h
r = g.send(666)
print r

输出:
start send test!
hello world
start second call...
recieved value from send: 666 
paused at 1 yield
1

send方法用来给生成器方法传递value。在首次调用g.send(None)时,生成器函数启动,返回第一个yield后边的值,也就是”hello world”,并将生成器函数挂起,继续执行主程序部分print h输出helloworld,接着g.send(666),生成器函数继续执行,输出start second call部分。第二次调用时,将当前挂起的整个yield表达式即yield “hello world”赋值为发送过去的值666,并返回下一个yield后边的值,然后再次暂停,如此往复,直到无值可返回,此时也需要处理StopIteration异常。

需要注意一点的是在首次调用send的时候,一定要传None,因为首次调用时,生成器函数刚启动,还没有处于挂起状态的yield,所以如果传递一个有效值过去没有yield接收。就好比你大半夜打电话给移动客服,肯定无人接听….

看例子

def test_send():
    print "start send test!"
    r = yield "hello world"
    print "start second call..."
    print "recieved value from send: %s " % r
    for i in range(1 canada goose jackets invade trendy city streets https://www.canadagooseparkaclearances.com Canada Goose coats outlet fake,3):
        print "paused at %d yield" % i
        yield i

g = test_send()
g.send(123)

输出:
TypeError: can't send non-None value to a just-started generator

中场总结:通过上述例子来看,send和next方法基本相同,都可以调用生成器函数,并使其挂起然后切回到调用者程序,并将yield后的值返回;都会发送一个值给当前yield表达式,只不过next无法自定义该值,一直发送的是None,而send可以发送自定义值。

3、throw()方法

#generator.throw(type[, value[, traceback]])
def test_throw():
    print "start throw test!"
    try:
        r = yield 999
    except TypeError, e:
        print e.message
    for i in range(1,3):
        yield i
        print 444

g = test_throw()
g.next()    
r = g.throw(TypeError, "haha,I catched TypeError!")  
print r

输出:
start throw test!
haha,I catched TypeError!
1

throw方法用来给生成器函数中当前挂起的yield表达式处传递一个异常,然后继续执行并返回下一个yield后的值,并再次挂起。如果不捕获异常,程序就该退出了。

4、close()方法

#generator.close()
def test_close():
    print "start close test!"
    try:
        r = yield 666
    except GeneratorExit,e:
        print "close by g.close()"
    except Exception,e:
        print "close by another error!"

g = test_close()
g.next()
g.close()   #在当前yield处引发GeneratorExit异常

输出:
start close test!
close by g.close()

close方法会在当前挂起的yield出引发GeneratorExit异常,退出生成器函数并将执行权返回给调用者程序。其实不需要捕获该异常也会正常退出,作为演示所以在示例代码中手动捕获了该异常。关于生成器函数关闭出,StopIteration同GeneratorExit一样的效果,退出生成器函数并将执行权返回给调用者程序。

以上就是生成器函数的4个方法的简单示例与鄙人的浅显的见解说明,下边上个复杂点的实际应用demo供大家揣摩把玩:

#coding:utf-8

from Queue import Queue
from functools import wraps

class Async(object):
    def __init__(self, func,args):
	super(Async, self).__init__()
	self.func = func
	self.args = args
		
def apply_async(func,args,callback):
    result = func(*args)
    callback(result)

def async_decorator(func):
    @wraps(func)
    def wrapper(*args):
	f = func(*args)
	result_queue = Queue()
	result_queue.put(None)
	while True:
	    result = result_queue.get()
	    try:
		a = f.send(result)	
		print "async continue..."
		apply_async(a.func, a.args, callback=result_queue.put)
	    except StopIteration, e:
		break
    return wrapper

def add(x,y):
    return x+y

@async_decorator
def start():
    print "start test"
    r = yield Async(add, (1,2))
    print r
    r = yield Async(add, ("hello"," world"))
    print r
    for n in range(10):
	r = yield Async(add, (n,n))
	print r
    print "good work!"

if __name__ == '__main__':
    start()

以上示例都是基于python2.7,python3中还有个yield from表达式此处暂且按下不表,囧~~

文章中有哪里讲的不对的地方或是有任何问题,欢迎留言纠正~

Comments are closed.