[关闭]
@spiritnotes 2016-03-13T16:49:17.000000Z 字数 3920 阅读 2092

Python语言实践分析 -- yield表达式

Python


生成器

使用列表

假设你要在通过某个操作获得一系列的值,例如我要生成0到n之间的数目,怎么做?用列表就可以的,代码如下:

  1. def get_num(n):
  2. l = []
  3. for i in range(0, n):
  4. l.append(i)
  5. return i

这个时候我们就可以直接迭代使用了

  1. for i in get_num(n):
  2. print(i)

定义类

如果假设产生的n数值很大,这种情况下会需要很多的内存,如果再大,还可能造成内存移除,那么该怎么办?简单,产生一个使用一个,通过定义类来实现

  1. class product_num():
  2. def __init__(self, n):
  3. self.max_ = n
  4. self.curr = 0
  5. def next(self):
  6. t = self.curr
  7. if t < n:
  8. self.curr += 1
  9. return t
  10. else:
  11. raise Exception('reach end')

现在就可以如下使用

  1. products = product_num(i)
  2. try:
  3. while True:
  4. i = products.next()
  5. print(i)
  6. except:
  7. pass

以前一个for循环就可以搞定的事情突然变得这么麻烦,怎么办?扩充迭代器协议,让for语句能够自动调用next,并处理异常,这样不就可以了么?于是又可以简单书写代码了

yield的产生

还是觉得麻烦了,为了实现该功能还要写一个类,何不让程序产生一个数字的时候就暂停呢,然后待外层执行完后,再执行产生新的一个数字呢。于是就产生了yield,我们可以这样写代码了

  1. def product_num(n):
  2. for i in range(n):
  3. yield i
  4. for i in product_num(n):
  5. print(i)

性质

生成器

含yield表达式的函数调用后返回的对象称为生成器,它是一种可迭代对象,因此可以用在各种迭代对象可以使用地方,如for。

生成器对象里面返回的值是实际执行的,并且只会执行一次。对于如下语句,第二次执行就会出错。

  1. def t():
  2. return list(range(5))
  3. x = t()
  4. ret = sum(x) / max(x)
  5. print(ret)
  6. def t2():
  7. for i in range(5):
  8. yield i
  9. x = t2()
  10. ret = sum(x) / max(x)
  11. print(ret)
  12. >>> Traceback (most recent call last):
  13. File "/...py", line 11, in <module>
  14. ret = sum(x) / max(x)
  15. ValueError: max() arg is an empty sequence

StopIteration

生成器正常执行结束的时候,会产生一个StopIteration异常,而在后面再次调用next时仍然同样产生该异常

  1. def stopiteration_test():
  2. def f():
  3. yield 2
  4. g = f()
  5. for i in range(5):
  6. try:
  7. print(next(g))
  8. except Exception as e:
  9. print(e.__class__.__name__)

结果如下

  1. 2
  2. StopIteration
  3. StopIteration
  4. StopIteration
  5. StopIteration

注意for语句只对该异常自动处理,如果其他异常仍然会触发正常的异常处理流程。

send

  1. def yield_test_send():
  2. def f():
  3. print('inner before')
  4. y = yield
  5. print('inner mid')
  6. z = yield y+1
  7. print('inner end z: ')
  8. yield z+1
  9. g = f()
  10. print('out first next', next(g))
  11. print('out send 1', g.send(5))
  12. print('out send 2', g.send(10))

其执行结果如下:

  1. inner before
  2. out first next None
  3. inner mid
  4. out send 1 6
  5. inner end z:
  6. out send 2 11

throw

  1. def test_throw():
  2. def g():
  3. print('inner 1')
  4. yield 2
  5. yield 3
  6. yield 4
  7. ge = g()
  8. for i in range(4):
  9. try:
  10. ge.throw(Exception, 'throw test')
  11. except Exception as e:
  12. print(e, e.__class__.__name__)
  13. next(ge)

结果如下:

  1. throw test Exception
  2. throw test Exception
  3. throw test Exception
  4. throw test Exception
  5. Traceback (most recent call last):
  6. File "....py", line 98, in test_throw
  7. next(ge)
  8. StopIteration

可以看到throw的执行结果一样,而不管当前的生成器状态,但是只要throw执行后,生成器的状态会已发生改变,next只会返回StopIteration。对于close也是一样的。

close

close()函数会将生成器关闭,并返回None,而之前看到有说close() 在开始执行的时候抛出 GeneratorExit,与实际结果并不相符

  1. def test_close():
  2. def f():
  3. print('inner')
  4. yield 2
  5. yield 3
  6. g = f()
  7. for i in range(4):
  8. try:
  9. print(g.close())
  10. print(next(g))
  11. except Exception as e:
  12. print(e.__class__.__name__)

执行结果如下

  1. g.close() None
  2. except: StopIteration
  3. g.close() None
  4. except: StopIteration
  5. g.close() None
  6. except: StopIteration
  7. g.close() None
  8. except: StopIteration

yield 与 return

当我们定义的函数里面同时含有yield和return的时候就需要注意了,有可能结果出乎意外

  1. def yield_with_return():
  2. def f():
  3. return 1
  4. yield 2
  5. def g():
  6. yield 2
  7. return 4
  8. for i in f():
  9. print('return before yield', i)
  10. for i in g():
  11. print('yield before before', i)
  12. print('return before yield, what if return value', f())

执行的结果如下:

  1. # 当return在yield之前时无输出
  2. yield before before 2 # return的值未输出
  3. return before yield, what if return value <generator...> # 含有yield表示式的函数产生的对象始终是生成器

从结果可以看到如下两点:

应用 contextlib @contextmanager 模拟 with 语句

我们知道要使用with语句,对象则必须实现自己的__enter__,__exit__函数。结合contextlib中的contextmanager以及yield可以让我们实现with语句的效果,yield作为分割符,前面模拟__enter__,后端模拟__exit__。

  1. def test_contextmanager():
  2. from contextlib import contextmanager
  3. @contextmanager
  4. def f():
  5. print('before')
  6. yield 2
  7. print('end')
  8. with f() as k:
  9. print('k', k)

运行结果如下

  1. before
  2. k 2
  3. end

基于生成器的协程

可以看如下实例

  1. # 消费者生成者实例
  2. def consumer():
  3. while True:
  4. word = yield
  5. print(word.upper())
  6. def producter(line):
  7. for word in line.split():
  8. yield word
  9. c = consumer()
  10. p = producter('i am very okay')
  11. next(c)
  12. for word in p:
  13. c.send(word)

greenlet

greenlet,C语言写成的协程库,与yield无关,greenlet与libevent/libev结合形成的gevent能够获得较大性能提升

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注