大师兄的Python学习笔记(十四): 迭代器、生成器和协程
2020-03-24 本文已影响0人
superkmi
大师兄的Python学习笔记(十三): 理解装饰器
大师兄的Python学习笔记(十五): Socket编程
一、关于迭代器(Iterator)
1. 可迭代对象
- 可直接作用于for循环的数据类型就叫可迭代对象(内置
__iter__
方法)。 - 可迭代对象分为两类:集合数据类型和生成器。
1.1 集合数据类型
- 比如: dict、str、list、tuple、set等数据类型。
# dict
>>>d = dict(a=1,b=2)
>>>for i in d:
>>> print(i)
a
b
# list
>>>l = list([1,2,3,4,5])
>>>for i in l:
>>> print(i)
1
2
3
4
5
# string
>>>s = 'Hello World!'
>>>for i in s:
>>> print(i)
H
e
l
l
o
W
o
r
l
d
!
# tuple
>>>t = (1,2,3,4,5)
>>>for i in t:
>>> print(i)
1
2
3
4
5
# set
>>>s = {1,2,3,4,5}
>>>for i in s:
>>> print(i)
1
2
3
4
5
1.2 生成器(generator)
- 生成器是一种边循环边计算的机制。
- 在函数中使用
yield
关键字返回值而不是return
。 -
yield
不会像return
一样停止程序,而是会将本次循环的代码执行完。
>>>def gen_workingday():
>>> days = ['mon','tue','wed','thu','fri']
>>> for d in days:
>>> yield d # 每次迭代的代码会储存在这里
>>>for d in gen_workingday():
>>> print("today is {}".format(d))
today is mon
today is tue
today is wed
today is thu
today is fri
1.3 生成器表达式
- 可以用生成器表达式的方式生成生成器。
- 生成器表达式的语法与列表推导式类似,只是把
[]
改成了()
。
>>>days = ['mon','tue','wed','thu','fri']
>>>wd = (d for d in days)
>>>while True:
>>> try:
>>> print("today is {}".format(next(wd)))
>>> except StopIteration:
>>> print("End of generator!")
>>> break
today is mon
today is tue
today is wed
today is thu
today is fri
End of generator!
2. 迭代器
- 可以被
next()
函数调用(或包含__next__
方法的)并不断返回下一个值的对象称为迭代器。 - 如果
next()
没有下一个值,则抛出异常:StopIteration
。
>>>def gen_workingday():
>>> days = ['mon','tue','wed','thu','fri']
>>> for d in days:
>>> yield d
>>> print("today is {}".format(d)) # 放在这里还是会被执行
>>>wd = gen_workingday()
>>>while True:
>>> try:
>>> next(wd)
>>> except StopIteration:
>>> print("End of generator!")
>>> break
today is mon
today is tue
today is wed
today is thu
today is fri
End of generator!
- 迭代器的好处:省资源。
- 迭代器并不会事先执行计算结果,而是每次迭代时进行计算。
二、关于协程(Coroutine)
- 协程是为非抢占式多任务产生子程序的计算机程序组件。
- 协程允许不同入口点在不同位置暂停或开始执行程序。
- 协程只有一个线程。
- 其实可以理解为生成器的工作机制。
1. 协程代码的实现
-
yield
关键字,与生成器中的功能相似但不同,用法是:<var> = yield
,协承被触发时会将值传给<var>
并从yield
后的代码继续执行。 -
send
关键字, 用于协程预激和触发。(类型生成器中的next
) - 协程在启动时需要用
send(None)
预激活。 - 一个简单的例子用来理解协程:
>>>def cor():
>>> print("start")
>>> x = yield
>>> print("预激\n")
>>> y = yield 1
>>> print("第一次yield\n")
>>> z = yield 2
>>> print("第二次yield\n")
>>>if __name__ =="__main__": # 主进程
>>> c = cor()
>>> c.send(None) # 预激协程
>>> try:
>>> a = c.send(1) # 调用协程
>>> print("received_a:",a)
>>> b = c.send(2)
>>> print("received_b:",b)
>>> c = c.send(3) # 这里会触发exception,所以后面的print不会被执行
>>> print("received_c:",c)
>>> except StopIteration as e:
>>> print("StopIteration触发",e)
start
预激
received_a: 1
第一次yield
received_b: 2
第二次yield
StopIteration触发
- 协程版的生产者-消费者模型:
>>>def producer(cons):
>>> cons.send(None) # 第3步, 预激活
>
>>> for n in range(1,5): # 第7步
>>> print("正在生产第{}件产品。".format(n)) # 第8步
>>> c = cons.send(n) # 第9步, 触发生成器并返回值
>>> print(c) # 第13步
>>> cons.close()
>
>>>def consumer():
>>> r = "" # 第4步
>>> while True: # 第5步
>>> n = yield r # 第6步, 返回并切换到producer / # 第12步,将r作为值返回
>>> if not n: # 第10步
>>> return
>>> r = "正在消费第{}件产品。".format(n) # 第11步
>
>>>if __name__ == "__main__":
>>> c = consumer() # 第1步, 构造生成器
>>> producer(c) # 第2步, 调用函数
正在生产第1件产品。
正在消费第1件产品。
正在生产第2件产品。
正在消费第2件产品。
正在生产第3件产品。
正在消费第3件产品。
正在生产第4件产品。
正在消费第4件产品。
2. 协程的四种状态
- 可以使用inspect包的getgeneratorstate模块查看协程状态。
- 协程包含四种状态:
状态 | 含义 |
---|---|
GEN_CREATED | 等待开始执行 |
GEN_RUNNING | 解释器正在执行 |
GEN_SUSPENDED | 在yield表达式处暂停 |
GEN_CLOSED | 执行结束 |
- 把前面的案例加上状态展示:
>>>from inspect import getgeneratorstate
>>>def cor(): # 简单的协程
>>> print("start")
>>> print("**状态:{}**".format(getgeneratorstate(c))) # GEN_RUNNING
>>> x = yield
>>> print("预激\n")
>>> y = yield 1
>>> print("第一次yield\n")
>>> z = yield 2
>>> print("第二次yield\n")
>>>if __name__ =="__main__": # 主进程
>>> c = cor()
>>> print("**状态:{}**".format(getgeneratorstate(c))) # GEN_CREATED
>>> c.send(None) # 预激协程
>>> try:
>>> print("**状态:{}**".format(getgeneratorstate(c))) # GEN_SUSPENDED
>>> a = c.send(1) # 调用协程
>>> print("received_a:",a)
>>> b = c.send(2)
>>> print("received_b:",b)
>>> c = c.send(3) # 这里会触发exception,所以后面的print不会被执行
>>> print("received_c:",c)
>>> except StopIteration as e:
>>> print("StopIteration触发",e)
>>> print("**状态:{}**".format(getgeneratorstate(c))) # GEN_CLOSED
**状态:GEN_CREATED**
start
**状态:GEN_RUNNING**
**状态:GEN_SUSPENDED**
预激
received_a: 1
第一次yield
received_b: 2
第二次yield
StopIteration触发
**状态:GEN_CLOSED**
3. 终止协程
3.1 方法一:通过抛出异常终止
1)直接抛出异常
- 通过
raise StopIteration
或generator.throw(<exception>)
方式直接抛出异常终止协程。>>>def cor(): # 简单的协程 >>> print("start") >>> x = yield >>> print("预激\n") >>> y = yield 1 >>> print("第一次yield\n") >>> z = yield 2 >>> print("第二次yield\n") >>>if __name__ =="__main__": # 主进程 >>> c = cor() >>> c.send(None) # 预激协程 >>> try: >>> a = c.send(1) # 调用协程 >>> print("received_a:",a) >>> c.throw(StopIteration) # 直接抛出异常 >>> b = c.send(2) >>> print("received_b:",b) >>> c = c.send(3) >>> print("received_c:",c) >>> except RuntimeError as e: >>> print("exception触发",e) start 预激 received_a: 1 exception触发 generator raised StopIteration
2)generator.close()
- 用
generator.close()
方法触发StopIteration
异常。>>>def cor(): # 简单的协程 >>> print("start") >>> x = yield >>> print("预激\n") >>> y = yield 1 >>> print("第一次yield\n") >>> z = yield 2 >>> print("第二次yield\n") >>>if __name__ =="__main__": # 主进程 >>> c = cor() >>> c.send(None) # 预激协程 >>> try: >>> a = c.send(1) # 调用协程 >>> print("received_a:",a) >>> c.close() # 终止协程 >>> b = c.send(2) >>> print("received_b:",b) >>> c = c.send(3) # 这里会触发exception,所以后面的print不会被执行 >>> print("received_c:",c) >>> except StopIteration as e: >>> print("StopIteration触发",e) start 预激 received_a: 1 StopIteration触发
3.2 方法二:通过哨符值
- 通过send()哨符值,判断终止协程。
- 通常使用None或Ellipsis作为哨符值。
>>>def cor(): # 子生成器
>>> print("start")
>>> x = yield
>>> print("预激\n")
>>> y = yield 1
>>> if y is Ellipsis: # 捕获哨符值,并终止协程,触发StopIteration
>>> return
>>> print("第一次yield\n")
>>> z = yield 2
>>> print("第二次yield\n")
>>>if __name__ =="__main__": # 主进程
>>> c = cor()
>>> c.send(None) # 预激协程
>>> try:
>>>
>>> a = c.send(1) # 调用协程
>>> print("received_a:",a)
>>> c.send(Ellipsis) # 发送哨符值
>>> b = c.send(2)
>>> print("received_b:",b)
>>> c = c.send(3) # 这里会触发exception,所以后面的print不会被执行
>>> print("received_c:",c)
>>> except StopIteration as e:
>>> print("StopIteration触发",e)
start
预激
received_a: 1
StopIteration触发
4. yield from
- 可以用来建立程序和生成器/协程之间的管道。
1)创建与生成器之间的双向管道
- 这里的逻辑是创建了一个生成器和主线程之间的管道,每次使用
yield from
,会调用一次连接的生成器。>>>d = {'a':1,'b':2,'c':3,'d':4,'e':5} >>>def gen(): >>> yield from d >>>g = gen() >>>while True: >>> try: >>> print(d[g.send(None)]) >>> except StopIteration: >>> print('end of gen.') >>> break 1 2 3 4 5 end of gen.
2)协程的委派生成器
- 如果理解了上一个案例,就可以理解协程之间的双向管道。
- 委托生成器只是将主线程
yield
的内容在主线程和协程中传递。- 委托生成器可以让代码更灵活间接,方便处理异常。
>>># 子协程 >>>def average_gen(): >>> print('cor started...') >>> while True: >>> x = yield >>> if x is None: # 哨兵值 >>> break >>> print('recieved:',x) >>> return x >>># 委托生成器 >>>def proxy_gen(): >>> while True: >>> x = yield from average_gen() # x 只有在yield完全结束才会被赋值 >>> if x is None: >>> break >>>if __name__ == "__main__": >>> gen = proxy_gen() >>> gen.send(None) >>> gen.send(1) >>> gen.send(2) >>> gen.send(3) >>> try: >>> gen.send(None) >>> except StopIteration: >>> print("end of proxy gen.") cor started... recieved: 1 recieved: 2 recieved: 3 end of proxy gen.
三、asyncio包
- asyncio包在python标准库中,内置了对异步IO的支持。
- asyncio本身是一个消息循环(eventloop)。
- 步骤: 创建消息循环->将协程导入->关闭
1. @asyncio.coroutine
- 将生成器标记为coroutine类型,便于导入消息循环。
2. asyncio.get_event_loop()
- 创建一个消息循环(eventloop)。
3. asyncio.sleep(<t>)
- 本身也是一个协程,可以看成是耗时<t>秒的程序。
4. EventLoop.run_until_complete(<coroutine>)
- 将协程抛到EventLoop中。
>>>import asyncio
>>>@asyncio.coroutine
>>>def cor():
>>> print("start cor...")
>>> r = yield from asyncio.sleep(5)
>>> print("hello world!")
>>>loop = asyncio.get_event_loop()
>>>loop.run_until_complete(cor())
>>>loop.close()
start cor...
hello world!
5. asyncio.wait(<tasks>)
- 将多个协程封装并抛到EventLoop中
>>>import asyncio
>>>@asyncio.coroutine
>>>def cor_a():
>>> print("start cor_a...")
>>> r = yield from asyncio.sleep(2)
>>> print("hello world from cor_a!")
>>>@asyncio.coroutine
>>>def cor_b():
>>> print("start cor_b...")
>>> r = yield from asyncio.sleep(5)
>>> print("hello world from cor_b!")
>>>task = [cor_a(),cor_b()]
>>>loop = asyncio.get_event_loop()
>>>loop.run_until_complete(asyncio.wait(task))
>>>loop.close()
start cor_a...
start cor_b...
hello world from cor_a!
hello world from cor_b!
6. asyncio.gather(<task1>,<task2>...)
- 将多个协程抛到EventLoop中。
>>>import asyncio
>>>@asyncio.coroutine
>>>def cor_a():
>>> print("start cor_a...")
>>> r = yield from asyncio.sleep(2)
>>> print("hello world from cor_a!")
>>>@asyncio.coroutine
>>>def cor_b():
>>> print("start cor_b...")
>>> r = yield from asyncio.sleep(5)
>>> print("hello world from cor_b!")
>>>loop = asyncio.get_event_loop()
>>>loop.run_until_complete(asyncio.gather(cor_a(),cor_b()))
>>>loop.close()
start cor_a...
start cor_b...
hello world from cor_a!
hello world from cor_b!
7. asyncio.ensure_future(<cor>)
- 将协程加入到task中,返回future对象。
- 按加入的顺序处理。
>>>import asyncio
>>>@asyncio.coroutine
>>>def cor_a():
>>> print("start cor_a...")
>>> r = yield from asyncio.sleep(2)
>>> print("hello world from cor_a!")
>>>@asyncio.coroutine
>>>def cor_b():
>>> print("start cor_b...")
>>> r = yield from asyncio.sleep(5)
>>> print("hello world from cor_b!")
>>>asyncio.ensure_future(cor_a())
>>>b = asyncio.ensure_future(cor_b())
>>>loop = asyncio.get_event_loop()
>>>loop.run_until_complete(b)
>>>loop.close()
start cor_a...
start cor_b...
hello world from cor_a!
hello world from cor_b!
8. async/await
- 新版本中,为了简化代码,可以使用
async/await
搭配替换代码。 - 用
async
替换@asyncio.coroutine
。 - 用
await
替换yield from
。
>>>import asyncio
>>>async def cor():
>>> print("start cor...")
>>> r = await asyncio.sleep(5)
>>> print("hello world!")
>>>loop = asyncio.get_event_loop()
>>>loop.run_until_complete(cor())
>>>loop.close()
start cor_a...
start cor_b...
hello world from cor_a!
hello world from cor_b!
9. asyncio.open_connection()
- 用协程处理网络连接数据流。
- 与request库的用法类似,可以用一个线程处理多个协程的数据流处理。
>>>import asyncio
>>>async def wget(host):
>>> print('wget {}'.format(host))
>>> connect = asyncio.open_connection(host,80)
>>> reader,writer = await connect
>>> header = "get / http/1.0\r\nHost: {}\r\n\r\n".format(host)
>>> writer.write(header.encode())
>>> await writer.drain()
>>> async for line in reader:
>>> print("{} header > {}".format(host,line.decode('unicode_escape').rstrip()))
>>>if __name__ == '__main__':
>>> hosts = ['www.baidu.com','www.sina.com.cn']
>>> wget_host = [wget(host) for host in hosts]
>>> loop = asyncio.get_event_loop()
>>> tasks = asyncio.wait(wget_host)
>>> loop.run_until_complete(tasks)
>>> loop.close()
wget www.baidu.com
wget www.sina.com.cn
www.baidu.com header > HTTP/1.1 400 Bad Request
www.baidu.com header >
www.sina.com.cn header > HTTP/1.1 400 Bad Request
www.sina.com.cn header > Server: nginx
www.sina.com.cn header > Date: Mon, 23 Mar 2020 12:17:27 GMT
www.sina.com.cn header > Content-Type: text/html
www.sina.com.cn header > Content-Length: 150
www.sina.com.cn header > Connection: close
www.sina.com.cn header > X-Via-CDN: f=edge,s=cnc.jinan.union.69.nb.sinaedge.com,c=2408:8207:24a6:5651:d49e:2689:d3af:6c65;
www.sina.com.cn header >
www.sina.com.cn header > <html>
www.sina.com.cn header > <head><title>400 Bad Request</title></head>
www.sina.com.cn header > <body>
www.sina.com.cn header > <center><h1>400 Bad Request</h1></center>
www.sina.com.cn header > <hr><center>nginx</center>
www.sina.com.cn header > </body>
www.sina.com.cn header > </html>
四、aiohttp包
- aiohttp是基于asyncio实现的HTTP框架。
- 以下案例创建了一个简单的http服务器:
>>>import asyncio
>>>from aiohttp import web
>>>async def index(request):
>>> await asyncio.sleep(1)
>>> return web.Response(body=b'<h1>Index</h1>')
>>>async def hello(request):
>>> await asyncio.sleep(1)
>>> text = '<h1>hello, %s!</h1>' % request.match._info['name']
>>> return web.Response(body=text.encode('utf-8'))
>>>async def start_server(loop):
>>> app = web.Application()
>>> app.router.add_route('GET','/',index)
>>> app.router.add_route('GET','/hello/{name}',hello)
>>> srv = await loop.create_server(web.AppRunner(app),'127.0.0.1',12000)
>>> print('Server started at http://127.0.0.1:12000...')
>>> return srv
>>>if __name__ == '__main__':
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(start_server(loop))
>>> loop.run_forever()
参考资料
- https://blog.csdn.net/u010138758/article/details/80152151 J-Ombudsman
- https://www.cnblogs.com/zhuluqing/p/8832205.html moisiet
- https://www.runoob.com 菜鸟教程
- http://www.tulingxueyuan.com/ 北京图灵学院
- http://www.imooc.com/article/19184?block_id=tuijian_wz#child_5_1 两点水
- https://blog.csdn.net/weixin_44213550/article/details/91346411 python老菜鸟
- https://realpython.com/python-string-formatting/ Dan Bader
- https://www.liaoxuefeng.com/ 廖雪峰
- https://blog.csdn.net/Gnewocean/article/details/85319590 新海说
- https://www.cnblogs.com/Nicholas0707/p/9021672.html Nicholas
- 《Python学习手册》Mark Lutz
- 《Python编程 从入门到实践》Eric Matthes
本文作者:大师兄(superkmi)