@spiritnotes
2016-03-13T16:49:17.000000Z
字数 3920
阅读 2257
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_ = n
self.curr = 0
def next(self):
t = self.curr
if t < n:
self.curr += 1
return t
else:
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 i
for 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 i
x = 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 2
g = f()
for i in range(5):
try:
print(next(g))
except Exception as e:
print(e.__class__.__name__)
结果如下
2
StopIteration
StopIteration
StopIteration
StopIteration
注意for语句只对该异常自动处理,如果其他异常仍然会触发正常的异常处理流程。
def yield_test_send():
def f():
print('inner before')
y = yield
print('inner mid')
z = yield y+1
print('inner end z: ')
yield z+1
g = f()
print('out first next', next(g))
print('out send 1', g.send(5))
print('out send 2', g.send(10))
其执行结果如下:
inner before
out first next None
inner mid
out send 1 6
inner end z:
out send 2 11
def test_throw():
def g():
print('inner 1')
yield 2
yield 3
yield 4
ge = 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 Exception
throw test Exception
throw test Exception
throw test Exception
Traceback (most recent call last):
File "....py", line 98, in test_throw
next(ge)
StopIteration
可以看到throw的执行结果一样,而不管当前的生成器状态,但是只要throw执行后,生成器的状态会已发生改变,next只会返回StopIteration。对于close也是一样的。
close()函数会将生成器关闭,并返回None,而之前看到有说close() 在开始执行的时候抛出 GeneratorExit,与实际结果并不相符
def test_close():
def f():
print('inner')
yield 2
yield 3
g = f()
for i in range(4):
try:
print(g.close())
print(next(g))
except Exception as e:
print(e.__class__.__name__)
执行结果如下
g.close() None
except: StopIteration
g.close() None
except: StopIteration
g.close() None
except: StopIteration
g.close() None
except: StopIteration
当我们定义的函数里面同时含有yield和return的时候就需要注意了,有可能结果出乎意外
def yield_with_return():
def f():
return 1
yield 2
def g():
yield 2
return 4
for 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
@contextmanager
def f():
print('before')
yield 2
print('end')
with f() as k:
print('k', k)
运行结果如下
before
k 2
end
可以看如下实例
# 消费者生成者实例
def consumer():
while True:
word = yield
print(word.upper())
def producter(line):
for word in line.split():
yield word
c = consumer()
p = producter('i am very okay')
next(c)
for word in p:
c.send(word)
greenlet,C语言写成的协程库,与yield无关,greenlet与libevent/libev结合形成的gevent能够获得较大性能提升