@spiritnotes
2016-03-13T16:49:17.000000Z
字数 3920
阅读 2461
Python
假设你要在通过某个操作获得一系列的值,例如我要生成0到n之间的数目,怎么做?用列表就可以的,代码如下:
def get_num(n):l = []for i in range(0, n):l.append(i)return i
这个时候我们就可以直接迭代使用了
for i in get_num(n):print(i)
如果假设产生的n数值很大,这种情况下会需要很多的内存,如果再大,还可能造成内存移除,那么该怎么办?简单,产生一个使用一个,通过定义类来实现
class product_num():def __init__(self, n):self.max_ = nself.curr = 0def next(self):t = self.currif t < n:self.curr += 1return telse:raise Exception('reach end')
现在就可以如下使用
products = product_num(i)try:while True:i = products.next()print(i)except:pass
以前一个for循环就可以搞定的事情突然变得这么麻烦,怎么办?扩充迭代器协议,让for语句能够自动调用next,并处理异常,这样不就可以了么?于是又可以简单书写代码了
还是觉得麻烦了,为了实现该功能还要写一个类,何不让程序产生一个数字的时候就暂停呢,然后待外层执行完后,再执行产生新的一个数字呢。于是就产生了yield,我们可以这样写代码了
def product_num(n):for i in range(n):yield ifor i in product_num(n):print(i)
含yield表达式的函数调用后返回的对象称为生成器,它是一种可迭代对象,因此可以用在各种迭代对象可以使用地方,如for。
生成器对象里面返回的值是实际执行的,并且只会执行一次。对于如下语句,第二次执行就会出错。
def t():return list(range(5))x = t()ret = sum(x) / max(x)print(ret)def t2():for i in range(5):yield ix = t2()ret = sum(x) / max(x)print(ret)>>> Traceback (most recent call last):File "/...py", line 11, in <module>ret = sum(x) / max(x)ValueError: max() arg is an empty sequence
生成器正常执行结束的时候,会产生一个StopIteration异常,而在后面再次调用next时仍然同样产生该异常
def stopiteration_test():def f():yield 2g = f()for i in range(5):try:print(next(g))except Exception as e:print(e.__class__.__name__)
结果如下
2StopIterationStopIterationStopIterationStopIteration
注意for语句只对该异常自动处理,如果其他异常仍然会触发正常的异常处理流程。
def yield_test_send():def f():print('inner before')y = yieldprint('inner mid')z = yield y+1print('inner end z: ')yield z+1g = f()print('out first next', next(g))print('out send 1', g.send(5))print('out send 2', g.send(10))
其执行结果如下:
inner beforeout first next Noneinner midout send 1 6inner end z:out send 2 11
def test_throw():def g():print('inner 1')yield 2yield 3yield 4ge = g()for i in range(4):try:ge.throw(Exception, 'throw test')except Exception as e:print(e, e.__class__.__name__)next(ge)
结果如下:
throw test Exceptionthrow test Exceptionthrow test Exceptionthrow test ExceptionTraceback (most recent call last):File "....py", line 98, in test_thrownext(ge)StopIteration
可以看到throw的执行结果一样,而不管当前的生成器状态,但是只要throw执行后,生成器的状态会已发生改变,next只会返回StopIteration。对于close也是一样的。
close()函数会将生成器关闭,并返回None,而之前看到有说close() 在开始执行的时候抛出 GeneratorExit,与实际结果并不相符
def test_close():def f():print('inner')yield 2yield 3g = f()for i in range(4):try:print(g.close())print(next(g))except Exception as e:print(e.__class__.__name__)
执行结果如下
g.close() Noneexcept: StopIterationg.close() Noneexcept: StopIterationg.close() Noneexcept: StopIterationg.close() Noneexcept: StopIteration
当我们定义的函数里面同时含有yield和return的时候就需要注意了,有可能结果出乎意外
def yield_with_return():def f():return 1yield 2def g():yield 2return 4for i in f():print('return before yield', i)for i in g():print('yield before before', i)print('return before yield, what if return value', f())
执行的结果如下:
# 当return在yield之前时无输出yield before before 2 # return的值未输出return before yield, what if return value <generator...> # 含有yield表示式的函数产生的对象始终是生成器
从结果可以看到如下两点:
我们知道要使用with语句,对象则必须实现自己的__enter__,__exit__函数。结合contextlib中的contextmanager以及yield可以让我们实现with语句的效果,yield作为分割符,前面模拟__enter__,后端模拟__exit__。
def test_contextmanager():from contextlib import contextmanager@contextmanagerdef f():print('before')yield 2print('end')with f() as k:print('k', k)
运行结果如下
beforek 2end
可以看如下实例
# 消费者生成者实例def consumer():while True:word = yieldprint(word.upper())def producter(line):for word in line.split():yield wordc = consumer()p = producter('i am very okay')next(c)for word in p:c.send(word)
greenlet,C语言写成的协程库,与yield无关,greenlet与libevent/libev结合形成的gevent能够获得较大性能提升