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

时间:2022-11-01 19:12:12
Python(3)---从迭代器到异步IO
whenif 关注
2017.02.13 23:48* 字数 1750 阅读 250评论 0喜欢 8

目录

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 迭代器状态图

 
Python(3)---从迭代器到异步IO
图片来自网络

  实际上迭代器就是实现迭代功能,先初始化迭代器,利用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)——并发编程》中记录到的多线程与多进程也是该问题的处理方法之一。

 
Python(3)---从迭代器到异步IO
图片来自网络

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
个人博客whenif
欢迎各路同学互相交流

Python(3)---从迭代器到异步IO的更多相关文章

  1. Python 第七篇:异步IO&bsol;数据库&bsol;队列&bsol;缓存

    Gevent协程 Select\Poll\Epoll异步IO与事件驱动 Python连接Mysql数据库操作 RabbitMQ队列 Redis\Memcached缓存 Paramiko SSH Tws ...

  2. Python并发编程之初识异步IO框架:asyncio 上篇(九)

    大家好,并发编程 进入第九篇. 通过前两节的铺垫(关于协程的使用),今天我们终于可以来介绍我们整个系列的重点 -- asyncio. asyncio是Python 3.4版本引入的标准库,直接内置了对 ...

  3. Python 10 协程,异步IO&comma;Paramiko

    本节内容 Gevent协程 异步IO Paramiko 携程 协程,又称为微线程,纤程(coroutine).是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文 ...

  4. Python之协程、异步IO、redis缓存、rabbitMQ队列

    本节内容 Gevent协程 Select\Poll\Epoll异步IO与事件驱动 Python连接Mysql数据库操作 RabbitMQ队列 Redis\Memcached缓存 Paramiko SS ...

  5. python的协程和异步io【select&vert;poll&vert;epoll】

    协程又叫做微线程,协程是一种用户态的轻量级的线程,操作系统根本就不知道协程的存在,完全由用户来控制,协程拥有自己的的寄存器的上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来后, ...

  6. python学记笔记 2 异步IO

    在IO编程中,我们知道CPU的速度远远快于磁盘,网络IO,在一个线程中,CPU执行速度的代码非常快,然而遇到IO操作就需要阻塞 需要等待IO操作完成才能继续下一步的动作.这种情况叫做同步IO 在IO操 ...

  7. Python并发编程之实战异步IO框架:asyncio 下篇(十一)

    大家好,并发编程 进入第十一章. 前面两节,我们讲了协程中的单任务和多任务 这节我们将通过一个小实战,来对这些内容进行巩固. 在实战中,将会用到以下知识点: 多线程的基本使用 Queue消息队列的使用 ...

  8. Python并发编程之学习异步IO框架:asyncio 中篇(十)

    大家好,并发编程 进入第十章.好了,今天的内容其实还挺多的,我准备了三天,到今天才整理完毕.希望大家看完,有所收获的,能给小明一个赞.这就是对小明最大的鼓励了.为了更好地衔接这一节,我们先来回顾一下上 ...

  9. &lbrack;译&rsqb;Python中的异步IO&colon;一个完整的演练

    原文:Async IO in Python: A Complete Walkthrough 原文作者: Brad Solomon 原文发布时间:2019年1月16日 翻译:Tacey Wong 翻译时 ...

随机推荐

  1. 【java基础学习】网络编程

    网络编程 InetAddress tcp udp

  2. 性能更好的js动画实现方式&mdash&semi;&mdash&semi;requestAnimationFrame

    用js来实现动画,我们一般是借助setTimeout或setInterval这两个函数,css3动画出来后,我们又可以使用css3来实现动画了,而且性能和流畅度也得到了很大的提升.但是css3动画还是 ...

  3. ue4 build configuration的解释

    ue4的build系统,继承并发展了3代的一如既往的复杂.. 一.每个configuration由两部份组成:[(性能)模式]+[(内容)组成] 模式有:Debug,DebugGame,Develop ...

  4. Java学习----Math函数

    public class TestMath { public static void main(String[] args) { System.out.println(Math.E); System. ...

  5. BZOJ1642&colon; &lbrack;Usaco2007 Nov&rsqb;Milking Time 挤奶时间

    1642: [Usaco2007 Nov]Milking Time 挤奶时间 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 525  Solved: 30 ...

  6. 我的Python成长之路---第八天---Python基础(25)---2016年3月5日(晴)

    多进程 multiprocessing模块 multiprocessing模块提供了一个Process类来代表一个进程对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ...

  7. golang cronexpr定时任务包使用

    包获取 go get -u github.com/gorhill/cronexpr 创建一个定时任务 expr, err = cron.Parse("* * * * *"); 获得 ...

  8. Redis、Memcache、MongoDb的优缺点

    Redis.Memcache.MongoDb的优缺点 Redis优点 支持多种数据结构,如 string(字符串). list(双向链表).dict(hash表).set(集合).zset(排序set ...

  9. Jmeter常见问题及场景应用

    Jmeter作为工具来讲,已经是一个相对比较牛掰的工具,除了它能够支持那么多协议以及方法之外,更在与它的前置处理以及后置处理.同步监控的人性化.当然,所有的工具.框架都是作为业务的支撑,如果不能满足我 ...

  10. 微信支付(APP支付)-服务端开发(一)

    微信支付,首先需要注册一个商户平台公众账号,(网址:https://pay.weixin.qq.com/index.php/home/d_login) 目前微信支付的接入方式有四种方式:公众号支付,A ...