大师兄的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关键字,与生成器中的功能相似但不同,用法是:<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 StopIterationgenerator.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()

参考资料



本文作者:大师兄(superkmi)

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

推荐阅读更多精彩内容