Python 协程


迭代器与生成器

迭代器

  • 可迭代对象:在类里面定义__iter__方法,并使用该类创建的对象就是可迭代对象。

    • 判断是否为可迭代对象

      from collections import Iterable
      print(isinstance("ronie", Iterable))
      print(isinstance(['r', 'o', 'n', 'i', 'e'], Iterable))
      print(isinstance({'r', 'o', 'n', 'i', 'e'}, Iterable))
      print(isinstance(('r', 'o', 'n', 'i', 'e'), Iterable))
      print(isinstance({'r': 0, "o": 1, "n": 2, "i": 3, "e": 3}, Iterable))
      print(isinstance(0, Iterable))
      print(isinstance(True, Iterable))
      
      True
      True
      True
      True
      True
      False
      False
    • 自定义可迭代对象

      from collections.abc import Iterable
      
      
      class A:
          def __iter__(self):
          pass  # 应当返回一个迭代器
      
      
      a = A()
      print(isinstance(a, Iterable))  # True
  • 迭代器:一个类如果实现了迭代器协议,就可以称之为迭代器。在Python中,实现迭代器协议就是实现以下2个方法:

    • __iter__:这个方法返回对象本身,即self;iter函数获取可迭代对象的迭代器,会调用可迭代对象身上的__iter__方法。
    • __next__:这个方法每次返回迭代的值,在没有可迭代元素时,抛出StopIteration异常。next函数会获取迭代器中下一个值,会调用迭代器对象身上的__next__方法。
    class MyList(object):
        def __init__(self):
            self.my_list = list()
            self.index = 0
    
        def append_item(self, item):
            self.my_list.append(item)
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.index < len(self.my_list):
                self.index += 1
                return self.my_list[self.index - 1]
            else:
                raise StopIteration
    
    m = MyList()
    m.append_item(10)
    m.append_item(30)
    m.append_item(20)
    i = iter(m)
    print(next(i))
    print(next(i))
    print(next(i))
    
    # 10
    # 30
    # 20
    
  • for循环:

    • for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。
    • for item in Iterator 循环的迭代器,不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。

生成器

  1. 创建生成器

    • 生成器表达式:

      x = (i for i in range(3))
      print(type(x))
      print(next(x))
      
      # <class 'generator'>
      # 0
    • yield关键字:

      def func():
       f = [1, 1]
       while True:
           f.append(f[-1] + f[-2])
           yield f[-1]
      
      f = func()
      print(type(f))
      print(next(f))
      print(next(f))
      print(next(f))
      print(next(f))
      print(next(f))
      
      # <class 'generator'>
      # 2
      # 3
      # 5
      # 8
      # 13

协程

协程,又称微线程,纤程,也称为用户级线程,在不开辟线程的基础上完成多任务,也就是在单线程的情况下完成多任务,多个任务按照一定顺序交替执行。

yield

  • 协程的简单例子

    # 协程计算平均值
    def avg():
      average = 0
      nums = 0
      while True:
          x = yield average
          average = (average * nums + x) / (nums + 1)
          nums += 1
    
    
    a = avg()
    print(a.send(None))
    print(a.send(20))
    print(a.send(30))
    print(a.send(10))
    print(a.send(2))
    
    # 0
    # 20.0
    # 25.0
    # 20.0
    # 15.5
    • 协程使用生成器函数定义:定义体中有yield关键字。
    • yield在表达式中使用;如果协程只需从客户那里接收数据,那么产出的值是None——这个值是隐式指定的,因为yield关键字右边没有表达式。
    • 与创建生成器的方式一样,调用函数得到生成器对象。
    • 首先要调用next()函数,因为生成器还没启动,没在yield语句处暂停,所以一开始无法发送数据。
  • 预激活协程的装饰器

    def activate(func):
      def f(*args, **kwargs):
          d = func(*args, **kwargs)
          next(d)
          return d
      return f
    
    @activate
    def avg():
      average = 0
      nums = 0
      while True:
          x = yield average
          average = (average * nums + x) / (nums + 1)
          nums += 1
    
    a = avg()
    # print(a.send(None))
    print(a.send(20))
    print(a.send(30))
    print(a.send(10))
    print(a.send(2))
  • 终止协程和异常处理

    • 发送哨符值,让协程退出:由于未处理的异常会导致协程终止,可以选择发送某个会导致协程异常的值让协程退出。

      >>> def activate(func):
      ...     def f(*args, **kwargs):
      ...         d = func(*args, **kwargs)
      ...         next(d)
      ...         return d
      ...     return f
      ... 
      >>> 
      >>> @activate
      ... def avg():
      ...     average = 0
      ...     nums = 0
      ...     while True:
      ...         x = yield average
      ...         average = (average * nums + x) / (nums + 1)
      ...         nums += 1
      ... 
      >>> a = avg()
      >>> a.send(20)
      20.0
      >>> a.send(30)
      25.0
      >>> a.send(None)
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 7, in avg
      TypeError: unsupported operand type(s) for +: 'float' and 'NoneType'
      >>> a.send(40)
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      StopIteration
      >>> 
      • 使用@coroutine装饰器装饰的averager协程,可以立即开始发送值。
      • 发送的值不是数字,导致协程内部有异常抛出。
      • 由于在协程内没有处理异常,协程会终止。如果试图重新激活协程,会抛出StopIteration异常。
    • 显式的抛出异常:

      >>> def activate(func):
      ...     def f(*args, **kwargs):
      ...         d = func(*args, **kwargs)
      ...         next(d)
      ...         return d
      ...     return f
      ... 
      >>> 
      >>> @activate
      ... def avg():
      ...     average = 0
      ...     nums = 0
      ...     while True:
      ...         try:
      ...             x = yield average
      ...             average = (average * nums + x) / (nums + 1)
      ...             nums += 1
      ...         except Stop:
      ...             pass
      ... 
      >>> 
      >>> class Stop(Exception):
      ...     pass
      ... 
      >>> 
      >>> a = avg()
      >>> # print(a.send(None))
      >>> print(a.send(20))
      20.0
      >>> print(a.send(30))
      25.0
      >>> print(a.send(10))
      20.0
      >>> print(a.send(2))
      15.5
      >>> a.throw(Stop)
      15.5
      >>> a.send(10)
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      StopIteration
      
      
      • 如果显示抛入的异常没有处理,则协程会中止。
      • 可以调用.close()方法关闭协程。
  • 协程返回值

    >>> def activate(func):
    ...     def f(*args, **kwargs):
    ...         d = func(*args, **kwargs)
    ...         next(d)
    ...         return d
    ...     return f
    ... 
    >>> 
    >>> @activate
    ... def avg():
    ...     average = 0
    ...     nums = 0
    ...     while True:
    ...         x = yield average
    ...         if x is None:
    ...             break
    ...         average = (average * nums + x) / (nums + 1)
    ...         nums += 1
    ...     return average
    ... 
    >>> 
    >>> class Stop(Exception):
    ...     pass
    ... 
    >>> 
    >>> a = avg()
    >>> # print(a.send(None))
    >>> print(a.send(20))
    20.0
    >>> print(a.send(30))
    25.0
    >>> print(a.send(10))
    20.0
    >>> print(a.send(2))
    15.5
    >>> try:
    ...     a.send(None)
    ... except StopIteration as e:
    ...     print(e.value)
    ... 
    15.5
    >>> 
    
    • 使用return中止一个协程,并返回值。
    • return中止协程会导致StopIteration异常。

      >>> a.send(None)
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      StopIteration: 15.5
      
    • 异常对象的value属性保存着返回的值。
    • 使用try,catch语句捕获异常并获取返回值。

yield from

class MyList(object):
    def __init__(self):
        self.my_list = list()
        self.index = 0

    def append_item(self, item):
        self.my_list.append(item)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.my_list):
            self.index += 1
            return self.my_list[self.index - 1]
        else:
            raise StopIteration


def func(iterable):
    yield from iterable


m = MyList()
m.append_item(23)
m.append_item(12)
m.append_item(54)
print(list(func(m)))

# [23, 12, 54]
  • 迭代器(即可指子生成器)产生的值直接返还给调用者
  • 任何使用send()方法发给委派生产器(即外部生产器)的值被直接传递给迭代器。如果send值是None,则调用迭代器next()方法;如果不为None,则调用迭代器的send()方法。如果对迭代器的调用产生StopIteration异常,委派生产器恢复继续执行yield from后面的语句;若迭代器产生其他任何异常,则都传递给委派生产器。
  • 子生成器可能只是一个迭代器,并不是一个作为协程的生成器,所以它不支持.throw()和.close()方法,即可能会产生AttributeError 异常。
  • 除了GeneratorExit 异常外的其他抛给委派生产器的异常,将会被传递到迭代器的throw()方法。如果迭代器throw()调用产生了StopIteration异常,委派生产器恢复并继续执行,其他异常则传递给委派生产器。
  • 如果GeneratorExit异常被抛给委派生产器,或者委派生产器的close()方法被调用,如果迭代器有close()的话也将被调用。如果close()调用产生异常,异常将传递给委派生产器。否则,委派生产器将抛出GeneratorExit 异常。
  • 当迭代器结束并抛出异常时,yield from表达式的值是其StopIteration 异常中的第一个参数。
  • 一个生成器中的return expr语句将会从生成器退出并抛出 StopIteration(expr)异常。

await,async

协程与任务

声明:Hello World|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Python 协程


我的朋友,理论是灰色的,而生活之树是常青的!