没脚的雀

python的generator理解(2)--对协程概念的支持


python中的生成器主要经历三个发展阶段:

  1. 作为实现迭代器协议的一种方式而出现(yield语句)
  2. 增添方法,使生成器能够支持协程(增加close,send,throw方法,调整yield语句为yield表达式)
  3. 为了重构方便增加了yield from 语法

在这篇博文中,主要介绍用于第二点


关于协程

协程,coroutine, 这个名称相对于当代操作系统多任务的抢占式协作(随着时间片的耗尽,进程/线程将会被操作系统剥夺cpu的使用权,然后分配给其他进程/线程),协程是主动交出cpu的使用权。

在python的生成器概念提出来时,生成器的概念已经很接近协程的概念了,试想一下,一个yield语句将会暂停(挂起)当前函数的执行(主动让出cpu的使用权),将一个中间值返回给函数的调用者;而为了恢复(resume)函数的执行,调用者可以使用next(gen)使生成器在挂起的点继续执行。但是还是有以下几点区别:

  • 在恢复函数执行的时候,无法向生成器传递参数或者向生成器抛出一个异常
  • 无法在try/finally的try分句中使用yield, 导致生成器在弃用之后无法进行资源释放

丰富生成器的行为

为了使生成器更加接近于协程的概念,python在pep342中做出了以下的改变:

  • 将yield从原来的语句(statement)调整为表达式(expression)

  • 为生成器对象增加了以下几个方法:

    • send方法: send方法的参数作为yield表达式的值;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      def generator_function():
      value = yield "yield from func"
      print "receive outside the gen_obj:", value

      gen_obj = generator_function()
      print gen_obj.send(None)
      ## yield from func
      gen_obj.send(42) ## 42 将会作为 yield "yield from func" 表达式的值赋值给value
      ## receive outside the gen_obj: 42

    • throw方法:throw方法的参数是一个异常,该异常将会在生成器对象的yield表达式的位置被抛出;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      def generator_function():
      try:
      value = yield "yield from func"
      except ValueError:
      print "valueerror"

      >>> gen_obj = generator_function()
      >>> gen_obj.send(None)
      'yield from func'
      >>> gen_obj.throw(ValueError)
      valueerror ## 在gen_obj 中被捕捉,执行except的代码块

      Traceback (most recent call last): ## 然后向调用者抛出一个StopIteration异常,
      File "<pyshell#68>", line 1, in <module> ## 表示生成器已经结束
      gen_obj.throw(ValueError)
      StopIteration
      >>> gen_obj1 = generator_function()
      >>> gen_obj1.send(None)
      'yield from func'
      >>> gen_obj1.throw(AttributeError) ## 向gen_obj1抛出异常,由于AttributeError异常没有在
      ## 生成器内部被捕捉, 该AttributeError异常将会被向调 ## 用者抛出
      Traceback (most recent call last):
      File "<pyshell#74>", line 1, in <module>
      gen_obj1.throw(AttributeError)
      File "<pyshell#65>", line 3, in generator_function
      value = yield "yield from func"
      AttributeError

    • close方法: close方法主要为生成器提供外部资源释放的能力,该方法会在生成器被gc回收的时候调用。close方法相当于gen_obj.throw(GeneratorExit), 将向生成器对象抛出一个GeneratorExit异常表示生成器推出了,主要用于确保在try/finally结构的try分句包含yield表达式时,finally总能够被执行(显示调用close方法,或者在gc回收时,隐式调用close方法)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      def gen_func():
      yield "Hello"
      try:
      value = yield 42
      print '42'
      finally:
      print "finally"

      >>> gen_obj1 = gen_func()
      >>> gen_obj1.send(None)
      'Hello'
      >>> gen_obj1.throw(ValueErro)

      Traceback (most recent call last):
      File "<pyshell#82>", line 1, in <module>
      gen_obj1.throw(ValueErro)
      NameError: name 'ValueErro' is not defined
      >>> gen_obj2 = gen_func()
      >>> gen_obj2.send(None)
      'Hello'
      >>> gen_obj2.close()
      >>> gen_obj1 = gen_func()
      >>> gen_obj1.send(None)
      'Hello'
      >>> gen_obj1.close()
      >>> gen_obj2 = gen_func()
      >>> gen_obj2.send(None)
      'Hello'
      >>> gen_obj2.send(None)
      42
      >>> gen_obj2.close()
      finally
      >>> gen_obj3 = gen_func()
      >>> gen_obj3.send(None)
      'Hello'
      >>> gen_obj3.send(None)
      42
      >>> del gen_obj3
      finally

大佬给口饭吃咧