大师兄的Python学习笔记(十四): 迭代器、生成器和协程

大师兄的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关键字,与生成器中的功能相似但不同,用法是: = yield,协承被触发时会将值传给并从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 StopIterationgenerator.throw()方式直接抛出异常终止协程。
>>>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()
  • 本身也是一个协程,可以看成是耗时秒的程序。
4. EventLoop.run_until_complete()
  • 将协程抛到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()
  • 将多个协程封装并抛到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(,...)
  • 将多个协程抛到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()
  • 将协程加入到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 > 
www.sina.com.cn header > 400 Bad Request
www.sina.com.cn header > 
www.sina.com.cn header > 

400 Bad Request

www.sina.com.cn header >
nginx
www.sina.com.cn header > www.sina.com.cn header >

四、aiohttp包

  • aiohttp是基于asyncio实现的HTTP框架。
  • 以下案例创建了一个简单的http服务器:
>>>import asyncio
>>>from aiohttp import web

>>>async def index(request):
>>>    await asyncio.sleep(1)
>>>    return web.Response(body=b'

Index

') >>>async def hello(request): >>> await asyncio.sleep(1) >>> text = '

hello, %s!

' % 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)

你可能感兴趣的:(大师兄的Python学习笔记(十四): 迭代器、生成器和协程)