Python(3)---从迭代器到异步IO


目录

1. 迭代(iteration)与迭代器(iterator)
  1.1 构建简单迭代器
  1.2 调用next()
  1.3 迭代器状态图
2. 生成器(generator)
  2.1 创建简单生成器
  2.2 利用函数定义生成器
3. 协程
  3.1 概念理解
  3.2 实例
4. 异步IO
  4.1 概念理解
  4.2 实例


1 迭代(iteration)与迭代器(iterator)

迭代是重复反馈过程的活动,其目的通常是为了接近并到达所需的目标或结果。每一次对过程的重复被称为一次“迭代”,而每一次迭代得到的结果会被用来作为下一次迭代的初始值。(维基百科)

iterator是实现了iterator.__iter__()和iterator.__next__()方法的对象iterator.__iter__()方法返回的是iterator对象本身。

1.1 构建简单迭代器

In [146]: test_iter = iter([i for i in range(1,4)]) 

In [147]: test_iter
Out[147]: <list_iterator at 0x84002a1f60>

返回列表迭代器对象,实际上实现了iterator.__iter__()。

1.2 调用next()

In [148]: next(test_iter)
Out[148]: 1

In [149]: next(test_iter)
Out[149]: 2

In [150]: next(test_iter)
Out[150]: 3

In [151]: next(test_iter)
Traceback (most recent call last):

  File "<ipython-input-151-ca50863582b2>", line 1, in <module>
    next(test_iter)

StopIteration
In [152]: 

可以看出next()实际调用了iterator.__next__()方法,每次调用更新iterator状态,令其指向后一项,以便下一次调用并返回当前结果。

1.3 迭代器状态图

图片来自网络

  实际上迭代器就是实现迭代功能,先初始化迭代器,利用next()方法实现重复调用更新值,上次的终值时本次的初值。

2 生成器(generator)

通常带有yield的函数便称为生成器,yield是生成器执行的暂停恢复点,也是实现generator的__next__()方法的关键!可以对yield表达式进行赋值,也可以将yield表达式的值返回。简而言之,generator是以更优雅的方式实现的iterator。

2.1 创建简单生成器

其创建方法区别于列表创建方式,在此采用()而非[]

In [163]: test_gene = (x * x for x in range(1,4))

In [164]: test_gene
Out[164]: <generator object <genexpr> at 0x00000084002AD8E0>

In [166]: test_gene.__next__()
Out[166]: 1

In [167]: test_gene.__next__()
Out[167]: 4

In [168]: test_gene.__next__()
Out[168]: 9

In [169]: test_gene.__next__()
Traceback (most recent call last):

  File "<ipython-input-169-e6166353d257>", line 1, in <module>
    test_gene.__next__()

StopIteration

2.2 利用函数定义生成器

In [173]: def test_gene(a):
     ...:     print("第一步")
     ...:     yield a
     ...:     a += 1
     ...:     print("第二步")
     ...:     yield a
     ...:     a += 1
     ...:     print("第三步")
     ...:     yield a
     ...:     a += 1
     ...: 
     ...: 
     ...: g = test_gene(1)  

In [174]: g
Out[174]: <generator object test_gene at 0x0000008400295620>

In [175]: g.__next__()
第一步
Out[175]: 1

In [176]: g.__next__()
第二步
Out[176]: 2

In [177]: g.__next__()
第三步
Out[177]: 3

In [178]: g.__next__()
Traceback (most recent call last):

  File "<ipython-input-178-60e4a84be5d7>", line 1, in <module>
    g.__next__()

StopIteration

可以看出如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。在每次调用next()的时候执行:

  • 遇到yield语句返回;
  • 保留上下文环境(保留局部变量状态);
  • 再次执行时从上次返回的yield语句处继续执行。

总的来说生成器是一类特殊迭代器,一个产生值的函数 yield 是一种产生一个迭代器却不需要构建迭代器的精密小巧的方法。很明显可以看出生成器(Generator)是采用边循环边计算的机制,当我们只需访问一个大列表的前几个元素的情况下可以不必创建完整的list,从而节省大量的空间。

3 协程

3.1 概念理解

线程与进程,有自己的上下文,调度是由CPU来决定调度的;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制(程序员控制),其实就是在一个线程中切换子线程。
  相比多线程有如下好处:一是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,当线程数量越多,协程的性能优势就越明显。二是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
  协程、线程、进程在不同场景下的适用性不尽相同,在其他语言中,协程的其实是意义不大的多线程即可已解决I/O的问题,但是在python因为有GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,所以如果一个线程里面I/O操作特别多,协程就比较适用,如网络请求。

3.2 实例

Python中的协程是通过“生成器(generator)”的概念实现的。这里引用廖雪峰Python教程中的例子,并将其修改为定外卖场景:

def shop():
    '''定义商家(生成器)
    '''        
    print("[-商家-] 开始接单 ......")
    print("###############################")
    r = "商家第1次接单完成"       # 初始化返回结果,并在启动商家时,返回给消费者
    while True:    
        n = yield r  # (n = yield):商家通过yield接收消费者的消息,(yield r):返给结果  
        print("[-商家-] 正在处理第%s次订单 ......" % n)
        print("[-商家-] 第%s次订单正在配送中 ......" % n)
        print("[-商家-] 第%s次订单已送达" % n)
        r = "商家第%s次接单完成" % (n+1)     # 商家信息,下个循环返回给消费者

def consumer(g):  
    '''定义消费者
    @g:商家生成器
    '''       
    print("[消费者] 开始下单 ......")
    r = g.send(None)    # 启动商家生成器  
    n = 0
    while n < 5:
        n += 1
        print("[消费者] 已下第%s单" % n)
        print("[消费者] 接受商家消息:%s" % r)
        r = g.send(n)   # 向商家发送下单消息并准备接收结果。此时会切换到消费者执行
        print("###############################")
    g.close()           # 关闭商家生成器
    print("[消费者] 停止接单 ......")

if __name__ == "__main__":
    g = shop() 
    consumer(g)
[消费者] 开始下单 ......
[-商家-] 开始接单 ......
###############################
[消费者] 已下第1单
[消费者] 接受商家消息:商家第1次接单完成
[-商家-] 正在处理第1次订单 ......
[-商家-] 第1次订单正在配送中 ......
[-商家-] 第1次订单已送达
###############################
[消费者] 已下第2单
[消费者] 接受商家消息:商家第2次接单完成
[-商家-] 正在处理第2次订单 ......
[-商家-] 第2次订单正在配送中 ......
[-商家-] 第2次订单已送达
###############################
[消费者] 已下第3单
[消费者] 接受商家消息:商家第3次接单完成
[-商家-] 正在处理第3次订单 ......
[-商家-] 第3次订单正在配送中 ......
[-商家-] 第3次订单已送达
###############################
[消费者] 已下第4单
[消费者] 接受商家消息:商家第4次接单完成
[-商家-] 正在处理第4次订单 ......
[-商家-] 第4次订单正在配送中 ......
[-商家-] 第4次订单已送达
###############################
[消费者] 已下第5单
[消费者] 接受商家消息:商家第5次接单完成
[-商家-] 正在处理第5次订单 ......
[-商家-] 第5次订单正在配送中 ......
[-商家-] 第5次订单已送达
###############################
[消费者] 停止接单 ......

4 异步IO实例

4.1 概念理解

异步是区别于同步,这里的同步指的并不是所有线程同时进行,而是所有线程在时间轴上有序进行。在实际的IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。异步正是为解决CPU高速执行能力和IO设备的龟速严重不匹配,当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。
  异步IO是基于CPU与IO处理速度不一致并为了充分利用资源的方法之一,在上一篇《Python知识(1)——并发编程》中记录到的多线程与多进程也是该问题的处理方法之一。

图片来自网络

4.2 实例

只有协程还不够,还不足以实现异步IO,我们必须实现消息循环和状态的控制,在此我们先了解一下几个关键词。

  • asyncio
    Python 3.4版本引入的标准库,直接内置了对异步IO的支持。asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

  • async/await
    python3.5中新加入的特性, 将异步从原来的yield 写法中解放出来,变得更加直观。其中async修饰的函数为异步函数,await 替换了yield from, 表示这一步为异步操作。

  • aiohttp
    一个提供异步web服务的库,分为服务器端和客户端。这里主要使用其客户端。

import asyncio
import aiohttp
async def get(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            print(url, resp.status)
            print(url, await resp.text())

loop = asyncio.get_event_loop()     # 得到一个事件循环模型
urls = ["https://movie.douban.com/tag/科幻?start="+str(1)+"&type=T" for i in range(1,4)]
tasks = [ get(url) for url in urls] # 初始化任务列表

loop.run_until_complete(asyncio.wait(tasks))    # 执行任务
loop.close()                        # 关闭事件循环列表

参考与拓展阅读:
[1]Python生成器详解 | 投稿
[2]廖雪峰Python教程
[3]Python学习:异步IO:协程和asyncio
[4]Python【第十篇】协程、异步IO
[5]Python进阶:理解Python中的异步IO和协程(Coroutine),并应用在爬虫中
[6]异步爬虫: async/await 与 aiohttp的使用,以及例子
[7]Python 异步网络爬虫(1)


个人Github
个人博客DebugNLP
欢迎各路同学互相交流

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容