基本定義
可迭代對象
可迭代對象(Iterable
):可以直接作用於for
循環的對象統稱爲可迭代對象。可以使用isinstance()
判斷一個對象是否是Iterable
對象。
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
迭代器
迭代器(Iterator
): Python
的Iterator
對象表示的是一個數據流,Iterator
對象可以被next()
函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration
錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()
函數實現按需計算下一個數據,所以Iterator
的計算是惰性的,只有在需要返回下一個數據時它纔會計算。
Iterator
甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。
生成器
生成器(generator
):生成器不但可以作用於for
循環,還可以被next()
函數不斷調用並返回下一個值,直到最後拋出StopIteration
錯誤表示無法繼續返回下一個值了。我們可以使用isinstance()
判斷一個對象是否是Iterator
對象:
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
yield
牛津詞典中對yield
定義一般有2層含義:產出 和讓步 ,對於Python來講如果我們在一段代碼中使用yield value
確實這2層含義都成立。首先,yield value
確實會產出value
給到調用next(...)
方法的調用方;並且還會作出讓步,暫停執行yield value
之後代碼,讓步給調用方繼續執行。直到調用方需要下一個值時再次調用next(...)
方法。調用方會從yield value
中取到value
。
如果我們在代碼中使用var = yield
也可以從調用方獲取到數據,不過需要調用方調用.send(data)
將數據傳輸到var
而不是next(...)
方法。
yield
關鍵字甚至可以不接受或者產出數據,不管數據如何流動,yield
都是一種流程控制工具,使用它可以實現協作式多任務;協程可以把控制器讓步給中心調度程序,從而激活其他協程。
協程基本案例
下面是一段simple_coroutine
協程和main
交互切換執行的過程演示,simple_coroutine
協程和main
只要有一方隨機產出6
則終止執行,否則就會一直切換執行
import random
def simple_coroutine():
print('coroutine started')
while True:
send = random.choice(range(8))
print('coroutine send',send)
if send == 6:
yield send
break
receive = yield send
print('coroutine receive', receive)
if __name__ == '__main__':
print('main started')
coroutine = simple_coroutine()
print('main declare',coroutine)
receive = next(coroutine)
print('main next',receive)
while receive != 6:
send = random.choice(range(8))
print('main send', send)
if send == 6:
coroutine.send(send)
coroutine.close()
break
receive = coroutine.send(send)
print('main receive',receive)
結果如下:
#/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
main started
main declare <generator object simple_coroutine at 0x10c862480>
coroutine started
coroutine send 6
main next 6
或者
#/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
main started
main declare <generator object simple_coroutine at 0x10c1d8480>
coroutine started
coroutine send 0
main next 0
main send 1
coroutine receive 1
coroutine send 3
main receive 3
main send 2
coroutine receive 2
coroutine send 6
main receive 6
通過上面的執行結果我們可以得到的結論是:
- 協程使用生成器的函數定義:定義體中有
yield
關鍵字。 - 帶有
yield
的函數不再是一個普通函數,Python
解釋器會將其視爲一個generator
。 coroutine = simple_coroutine()
與創建生成器的方式一樣,調用函數只會得到生成器對象<generator object simple_coroutine at 0x10c1d8480>
,並不會開始執行協程代碼。- 我們需要先調用
next(coroutine)
函數,因爲得到的生成器對象還沒啓動沒在yield
處暫停,我們無法調用coroutine.send(send)
發送數據。 - 當我們調用
next(coroutine)
函數後,得到的生成器對象會啓動執行到yield send
處產出receive
,然後終止執行,讓步給調用方main
繼續執行,直到調用方main
需要下一個值時調用receive = coroutine.send(send)
讓步給協程繼續執行。 value = yield
代表協程只需要從調用方接受數據,那麼產出的值爲None
,這個值是隱式指定的,因爲yield
右邊沒有關鍵字。
協程狀態
協程可以處於下面的四種狀態,協程當前的狀態可以使用inspect.getgeneratorstate(...)
函數得到:
GEN_CREATE
:創建,等待開始執行GEN_RUNNING
:生成器執行中GEN_SUSPENDED
:在yield表達式處暫停GEN_CLOSE
:執行結束,生成器關閉
此時,我們將main
修改如下:
if __name__ == '__main__':
print('main started')
coroutine = simple_coroutine()
print('main declare',coroutine)
print(getgeneratorstate(coroutine))
receive = next(coroutine)
print('main next',receive)
while receive != 6:
send = random.choice(range(8))
print('main send', send)
if send == 6:
coroutine.send(send)
print(getgeneratorstate(coroutine))
coroutine.close()
print(getgeneratorstate(coroutine))
break
receive = coroutine.send(send)
print(getgeneratorstate(coroutine))
print('main receive',receive)
執行結果如下:
#/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
main started
main declare <generator object simple_coroutine at 0x10ca96480>
GEN_CREATED
coroutine started
coroutine send 7
main next 7
main send 6
coroutine receive 6
coroutine send 5
GEN_SUSPENDED
GEN_CLOSED
或者
/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
main started
main declare <generator object simple_coroutine at 0x10effc480>
GEN_CREATED
coroutine started
coroutine send 3
main next 3
main send 2
coroutine receive 2
coroutine send 2
GEN_SUSPENDED
main receive 2
main send 7
coroutine receive 7
coroutine send 6
GEN_SUSPENDED
main receive 6
從上面的執行結果來看,我們可以得到一下結論
coroutine = simple_coroutine()
創建協程,協程處於GEN_CREATED
狀態,等待開始執行- 調用
next(coroutine)
後,協程處於GEN_RUNNING
執行中,直到遇到yield
產出值後處於GEN_SUSPENDED
暫停狀態 - 協程
break
執行完成或者調用coroutine.close()
後,協程處於GEN_CLOSE
:執行結束關閉狀態。
使用協程連續計算平均值
下面我們看一個使用協程連續計算平均值的例子,我們設置一個無限循環,只要調用方不斷的將值發給協程,他就會一直接受值,然後計算total和count。僅當調用方調用.close()
方法,或者程序沒有對協程的引用時被垃圾程序回收。
def average():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
if __name__ == '__main__':
coro_avg = average()
next(coro_avg)
no_list = []
for i in range(6):
no =random.choice(range(100))
no_list.append(no)
print(no_list,' avg: ',coro_avg.send(no))
執行結果
#/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
[51] avg: 51.0
[51, 45] avg: 48.0
[51, 45, 44] avg: 46.666666666666664
[51, 45, 44, 81] avg: 55.25
[51, 45, 44, 81, 49] avg: 54.0
[51, 45, 44, 81, 49, 50] avg: 53.333333333333336
協程返回值
爲了使協程返回值,我們必須要使協程可以正常終止。我們改造上面計算連續平均值的程序。
from collections import namedtuple
Result = namedtuple('Result','count average')
def average():
total = 0.0
count = 0
average = None
while True:
term = yield
if term is None:
break;
total += term
count += 1
average = total/count
return Result(count,average)
if __name__ == '__main__':
coro_avg = average()
next(coro_avg)
no_list = []
for i in range(6):
no =random.choice(range(100))
no_list.append(no)
coro_avg.send(no)
try:
coro_avg.send(None)
except StopIteration as e:
result = e.value
print(no_list,' avg: ',result)
執行結果如下:
#/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
[71, 2, 73, 55, 74, 67] avg: Result(count=6, average=57.0)
yield from
yield from
語法可以讓我們方便地調用另一個generator
。yield from Iterable
相當於for i in Iterable:yield i
yield from
結果會在內部自動捕獲StopIteration
異常。這種處理方式與for
循環處理StopIteration
異常的方式一樣。對於yield from
結構來說,解釋器不僅會捕獲StopIteration
異常,還會把value
屬性的值變成yield from
表達式的值。- 在函數外部不能使用
yield from
(yield
也不行)。
def gen():
for c in "AB":
yield c
for i in range(1,3):
yield i
def gen2():
yield from "AB"
yield from range(1,3)
def gen3():
yield from gen()
if __name__ == '__main__':
l_result = []
coro = gen()
while True:
try:
a = next(coro)
l_result.append(a)
except StopIteration as e:
break
print(l_result)
print(list(gen3()))
print(list(gen()))
print(list(gen2()))
執行結果
#/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
['A', 'B', 1, 2]
['A', 'B', 1, 2]
['A', 'B', 1, 2]
['A', 'B', 1, 2]
yield from與同步非阻塞
yield from
本身只是讓我們方便地調用另一個generator
,但是在阻塞網絡的情況下,我們可以利用yield from
和asyncio
實現異步非阻塞。
#複雜計算要一會
@asyncio.coroutine
def count_no():
return 2**1000
@asyncio.coroutine
def count(no):
print(time.time(),'count %d! %s' % (no,threading.currentThread()))
yield from count_no()
print(time.time(),'count %d end! %s' % (no,threading.currentThread()))
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks=[count(i) for i in range(100)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
執行結果如下:
#/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
1545651898.067989 count 9! <_MainThread(MainThread, started 140736385631168)>
1545651898.068131 count 9 end! <_MainThread(MainThread, started 140736385631168)>
1545651898.068198 count 75! <_MainThread(MainThread, started 140736385631168)>
1545651898.06828 count 75 end! <_MainThread(MainThread, started 140736385631168)>
1545651898.0683289 count 10! <_MainThread(MainThread, started 140736385631168)>
1545651898.068373 count 10 end! <_MainThread(MainThread, started 140736385631168)>
1545651898.068413 count 76! <_MainThread(MainThread, started 140736385631168)>
1545651898.068458 count 76 end! <_MainThread(MainThread, started 140736385631168)>
1545651898.068498 count 11! <_MainThread(MainThread, started 140736385631168)>
1545651898.068539 count 11 end! <_MainThread(MainThread, started 140736385631168)>
1545651898.068577 count 77! <_MainThread(MainThread, started 140736385631168)>
1545651898.0689468 count 77 end! <_MainThread(MainThread, started 140736385631168)>
1545651898.0690439 count 12! <_MainThread(MainThread, started 140736385631168)>
1545651898.069103 count 12 end! <_MainThread(MainThread, started 140736385631168)>
1545651898.069149 count 78! <_MainThread(MainThread, started 140736385631168)>
1545651898.069193 count 78 end! <_MainThread(MainThread, started 140736385631168)>
1545651898.0692348 count 13! <_MainThread(MainThread, started 140736385631168)>
1545651898.069278 count 13 end! <_MainThread(MainThread, started 140736385631168)>
1545651898.0693178 count 79! <_MainThread(MainThread, started 140736385631168)>
1545651898.069359 count 79 end! <_MainThread(MainThread, started 140736385631168)>
複雜計算操作可以達到我們的目標,但是網絡請求呢?
import asyncio
import requests
import threading
import time
#asyncio.coroutine包裝成generator
@asyncio.coroutine
def request_net(url):
return requests.get(url)
@asyncio.coroutine
def hello(url):
print(time.time(),'Hello %s! %s' % (url,threading.currentThread()))
resp = yield from request_net(url)
print(time.time(),resp.request.url)
print(time.time(),'Hello %s again! %s' % (url,threading.currentThread()))
if __name__ == '__main__':
loop = asyncio.get_event_loop()
#訪問12306模擬網絡長時間操作
tasks = [hello('https://kyfw.12306.cn'), hello('http://www.baidu.com')]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
執行結果如下
#/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
1545649725.3060732 Hello https://kyfw.12306.cn! <_MainThread(MainThread, started 140736385631168)>
1545649725.96431 https://kyfw.12306.cn/otn/passport?redirect=/otn/
1545649725.96435 Hello https://kyfw.12306.cn again! <_MainThread(MainThread, started 140736385631168)>
1545649725.9646132 Hello http://www.baidu.com! <_MainThread(MainThread, started 140736385631168)>
1545649726.023788 http://www.baidu.com/
1545649726.023826 Hello http://www.baidu.com again! <_MainThread(MainThread, started 140736385631168)>
可以發現和正常的請求並沒有什麼兩樣,依然還是順次執行的,12306那麼卡,百度也是等待返回後順序調度的,其實,要實現異步處理,我們必須要使用支持異步操作的請求方式纔可以實現真正的異步
async def get(url):
async with aiohttp.ClientSession() as session:
rsp = await session.get(url)
result = await rsp.text()
return result
async def request(url):
print(time.time(), 'Hello %s! %s' % (url, threading.currentThread()))
result = await get(url)
print(time.time(),'Get response from', url)
print(time.time(), 'Hello %s again! %s' % (url, threading.currentThread()))
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = [ asyncio.ensure_future(request('http://www.163.com/')),asyncio.ensure_future(request('http://www.baidu.com'))]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
執行結果如下:
#/usr/local/bin/python3.7 /data/code/python/test/coroutine/coroutinue.py
1545651249.1526 Hello http://www.163.com/! <_MainThread(MainThread, started 140736385631168)>
1545651249.1627488 Hello http://www.baidu.com! <_MainThread(MainThread, started 140736385631168)>
1545651250.150497 Get response from http://www.baidu.com
1545651250.1505191 Hello http://www.baidu.com again! <_MainThread(MainThread, started 140736385631168)>
1545651250.165774 Get response from http://www.163.com/
1545651250.165801 Hello http://www.163.com/ again! <_MainThread(MainThread, started 140736385631168)>
用asyncio
提供的@asyncio.coroutine
可以把一個generator
標記爲coroutine
類型,然後在coroutine
內部用yield from
調用另一個coroutine
實現異步操作。
爲了簡化並更好地標識異步IO
,從Python 3.5開始引入了新的語法async
和await
,可以讓coroutine
的代碼更簡潔易讀。