有一部分代码没有合并进来来

本章主要讲解协程问题,从迭代器开始逐渐过渡到携程的概念和用法。

迭代器

  • 可迭代:可直接被for循环使用的对象叫可迭代的(Iterable), 注意这个是个形容词,用来形容一个对象是否可以迭代。
  • 迭代器:不但可以作用于for循环,还可以被next调用的,叫迭代器(Itrator), 这个是一个名词。

通过定义我们知道,迭代器都是可迭代的,可迭代的未必是迭代器。

我们常见的range函数就是可迭代的,我们也经常用,还有比如list对象等。

  • 可以用isinstance判断是否可迭代和是否是迭代器,参看下面代码:

      from collections import Iterable
      l = [1,2,3,4]
      isinstance(l, Iterable)
    
        
      from collections import Iterator
      isinstance((x for x in range(10)), Iterator)
    
  • iterable和Iterator的关系,可以通过iter函数运算,简而言之就是把一个对象转换成一个迭代器

      isinstance(iter('abc'), Iterator)
    
  • for循环原理

    • 先判断对象是否为可迭代对象
      • 不是的话直接报错,抛出TypeError异常
      • 是的话则调用iter方法,返回一个迭代器
    • 不断地调用迭代器的next方法,每次按序返回迭代器中的一个值

    • 迭代到最后,没有更多元素了就抛出异常StopIteration,这个异常python 自己会处理,不会暴露给开发者

    • 看个小代码:

        # for循环原理
        x = [1, 2, 3]
        x_iterator = x.__iter__()
        try:
            while True:
                print(next(x_iterator)) #.__next__())
        except StopIteration:
            print("STOP")
      

生成器

生成器是特指一种一边循环一边计算的机制的实现(generator),是一种特定的迭代器。

它是一个算法/函数,这个函数可以返回多个返回值,想得到这个函数需要用next函数调用这个生成器, 每次调用next函数的时候生成器会计算下一个值,利用yield返回一个值,这样如果有多个yield这个 函数就能返回不同的值。

最后一次被调动,没有值返回的时候则抛出StopIteration异常。

生成器可以直接创建,如下面案例:

L = [x * x for x in range(10)] #生成列表
g = (x * x for x in range(10)) #得到一个生成器

第一个L是用列表生成式创建的列表,而第二个则是创建了一个生成器(generator),而不是一个元祖生成器,因为世上根本 就没有元祖生成器,用的人多了,也没有!

可以粗暴的理解成函数中包含yield叫generator,生成器可以被next调用,此时遇到函数中的yield则返回, 下次被next调用的时候则继续上次返回的点执行,这样一个函数可以被多次调用,每次调用执行一段代码。

生成器案例1:

 def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)
    
if __name__ == '__main__':
    try:
        g1 = odd()
        while True:
            a = next(g1)
            print(a)
    except StopIteration:
        print("odd DONE")

需要注意生成器的使用方法,我先调用了下生成器,得到一个生成器变量g1后,此时生成器函数odd()还没 真正运行,我需要调用next,每调用一次生成器会运行一段代码,到yield后就停止,此时返回值就是yield后面 的值,一直运行下去,直到最后发出StopIteration异常。

案例1的生成器运行结果如下:

step 1
1
step 2
3
step 3
5
odd DONE

利用生成器解决斐波那契数列问题:

#coding=utf-8
# 利用循环解决斐波那契问题
def fib_loop(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

# 利用生成器
def fib_g(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1


if __name__ == '__main__':
    print("循环的斐波那契:")
    fib_loop(5)

print("生成器的斐波那契:")
g = fib_g(5)
try:
    while True:
        a = next(g)
        print(a)
except StopIteration:
    print("STOP")

协程

  • 历史
    • 3.4引入协程概念,用yield实现
    • 3.5 引入协程语法
    • 实现包asyncio,tornado, gevent
  • 定义:“协程 是为非抢占式多任务产生子程序的计算机程序组件,
    • 协程允许不同入口点在不同位置暂停或开始执行程序”。
    • 从技术的角度来说,“协程就是你可以暂停执行的函数”。
    • 如果你把它理解成“就像生成器一样”,那么你就想对了。
  • 使用:
    • yield关键字
    • send关键字
  • 案例v1

  • 协程的四个状态
    • inspect.getgeneratorstate(…) 函数确定,该函数会返回下述字符串中的一个:
    • GEN_CREATED:等待开始执行
    • GEN_RUNNING:解释器正在执行
    • GEN_SUSPENED:在yield表达式处暂停
    • GEN_CLOSED:执行结束
    • next预激(prime)
    • 完整执行过程v2
  • 协程终止
    • 协程中未处理的异常会向上冒泡,传给 next 函数或 send 方法的调用方(即触发协程的对象)。
    • 终止协程的一种方式:发送某个哨符值,让协程退出。内置的 None 和Ellipsis 等常量经常用作哨符值==。
  • 异常
    • 客户段代码可以在生成器对象上调用两个方法
    • generator.throw(Exctpiton):

        致使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。
      
    • generator.close()

         致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。
         如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),
         调用方不会报错。如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。
      
    • v03
  • yield from
    • 为了得到返回值,协程必须正常终止;
    • 然后生成器对象会抛出StopIteration 异常,异常对象的 value 属性保存着返回的值。from
    • yield from 从内部捕获StopIteration异常
    • 并且把StopIteration异常value属性子作为yield from表达式的返回值
    • v04
    • v05
    • 委派生成器
      • 包含 yield from 表达式的生成器函数
      • 委派生成器在 yield from 表达式处暂停时,调用方可以直接把数据发给子生成器。
      • 子生成器再把产出的值发给调用方。
      • 子生成器返回之后,解释器会抛出 StopIteration 异常,并把返回值附加到异常对象上, 此时委派生成器会恢复。
      • v06

asyncio

  • python3.4开始引入的标准库,内置了对移步io的支持
  • asyncio本身是一个消息循环,
  • 步骤
    • 创建消息循环
    • 把协程导入
    • 关闭
  • 案例08
  • 案例09-两个tasks
  • 案例v10-得到多个网站

async and await

  • 为了更好的表示异步io
  • python3.5 开始引入
  • 让coroutine代码更简洁
  • 使用上,可以简单进行替换
    • 可以把 @asyncio.coroutine 替换成async
    • yield from 替换成await
  • 案例v11, 把案例09直接替换

aiohttp

  • 介绍
    • asyncio实现单线程并发IO,在客户端用处不大
    • 在服务器端可以asyncio+coroutine配合,因为http是io操作
    • asyncio实现了TCP,UIDP,SSL等协议
    • aiohttp是给予asyncio实现的HTTP框架
    • pip install aiohttp
    • 案例07

concurrent.futures

  • python3新增的库
  • 类似其他语言的线程池的概念
  • 此模块利用multiprocessiong实现真正的平行计算
  • 核心原理是:concurrent.futures会以子进程的形式,平行的运行多个python解释器,从而令python程序可以利用多核CPU来提升执行速度。 由于子进程与主解释器相分离,所以他们的全局解释器锁也是相互独立的。每个子进程都能够完整的使用一个CPU内核。
  • concurrent.futures.Executor
    • ThreadPoolExecutor
    • ProcessPoolExecutor
  • submit(fn, args, kwargs)
    • fn:异步执行的函数
    • args,kwargs:参数

         # 官方死锁案例
        import time
        def wait_on_b():
            time.sleep(5)
            print(b.result())  #b不会完成,他一直在等待a的return结果
            return 5
      
        def wait_on_a():
            time.sleep(5)
            print(a.result())  #同理a也不会完成,他也是在等待b的结果
            return 6
      
      
        executor = ThreadPoolExecutor(max_workers=2)
        a = executor.submit(wait_on_b)
        b = executor.submit(wait_on_a)
      
    • 案例v14
  • map(fn, *iterables, timeout=None)
    • 跟map函数类似
    • 函数需要异步执行
    • timeout: 超时时间
    • 案例 v12
    • 案例v15
    • 起执行结果是list,数据需要从list中取出来

        with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
            print(list(executor.map(sleeper, x)))
      
  • submit和map根据需要选一个即可
  • 案例v13

  • Future
    • 未来需要完成的任务
    • future 实例由Excutor.submit创建
    • 案例v17