pythonasyncwith
1. 詳解python中的協程,為什麼說它的底層是生成器
協程又稱為是微線程,英文名是Coroutine。它和線程一樣可以調度,但是不同的是線程的啟動和調度需要通過操作系統來處理。並且線程的啟動和銷毀需要涉及一些操作系統的變數申請和銷毀處理,需要的時間比較長。而協程呢,它的調度和銷毀都是程序自己來控制的,因此它更加輕量級也更加靈活。
協程有這么多優點,自然也會有一些缺點,其中最大的缺點就是需要編程語言自己支持,否則的話需要開發者自己通過一些方法來實現協程。對於大部分語言來說,都不支持這一機制。go語言由於天然支持協程,並且支持得非常好,使得它廣受好評,短短幾年時間就迅速流行起來。
對於Python來說,本身就有著一個GIL這個巨大的先天問題。GIL是Python的全局鎖,在它的限制下一個Python進程同一時間只能同時執行一個線程,即使是在多核心的機器當中。這就大大影響了Python的性能,尤其是在CPU密集型的工作上。所以為了提升Python的性能,很多開發者想出了使用多進程+協程的方式。一開始是開發者自行實現的,後來在Python3.4的版本當中,官方也收入了這個功能,因此目前可以光明正大地說,Python是支持協程的語言了。
生成器(generator)
生成器我們也在之前的文章當中介紹過,為什麼我們介紹協程需要用到生成器呢,是因為Python的協程底層就是通過生成器來實現的。
通過生成器來實現協程的原因也很簡單,我們都知道協程需要切換掛起,而生成器當中有一個yield關鍵字,剛好可以實現這個功能。所以當初那些自己在Python當中開發協程功能的程序員都是通過生成器來實現的,我們想要理解Python當中協程的運用,就必須從最原始的生成器開始。
生成器我們很熟悉了,本質上就是帶有yield這個關鍵詞的函數。
async,await和future
從Python3.5版本開始,引入了async,await和future。我們來簡單說說它們各自的用途,其中async其實就是@asyncio.coroutine,用途是完全一樣的。同樣await代替的是yield from,意為等待另外一個協程結束。
我們用這兩個一改,上面的代碼就成了:
async def test(k):
n = 0
while n < k:
await asyncio.sleep(0.5)
print('n = {}'.format(n))
n += 1
由於我們加上了await,所以每次在列印之前都會等待0.5秒。我們把await換成yield from也是一樣的,只不過用await更加直觀也更加貼合協程的含義。
Future其實可以看成是一個信號量,我們創建一個全局的future,當一個協程執行完成之後,將結果存入這個future當中。其他的協程可以await future來實現阻塞。我們來看一個例子就明白了:
future = asyncio.Future()
async def test(k):
n = 0
while n < k:
await asyncio.sleep(0.5)
print('n = {}'.format(n))
n += 1
future.set_result('success')
async def log():
result = await future
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
log(),
test(5)
]))
loop.close()
在這個例子當中我們創建了兩個協程,第一個協程是每隔0.5秒print一個數字,在print完成之後把success寫入到future當中。第二個協程就是等待future當中的數據,之後print出來。
在loop當中我們要調度執行的不再是一個協程對象了而是兩個,所以我們用asyncio當中的wait將這兩個對象包起來。只有當wait當中的兩個對象執行結束,wait才會結束。loop等待的是wait的結束,而wait等待的是傳入其中的協程的結束,這就形成了一個依賴循環,等價於這兩個協程對象結束,loop才會結束。
總結
async並不只是可以用在函數上,事實上還有很多其他的用法,比如用在with語句上,用在for循環上等等。這些用法比較小眾,細節也很多,就不一一展開了,大家感興趣的可以自行去了解一下。
不知道大家在讀這篇文章的過程當中有沒有覺得有些費勁,如果有的話,其實是很正常的。原因也很簡單,因為Python原生是不支持協程這個概念的,所以在一開始設計的時候也沒有做這方面的准備,是後來覺得有必要才加入的。那麼作為後面加入的內容,必然會對原先的很多內容產生影響,尤其是協程藉助了之前生成器的概念來實現的,那麼必然會有很多耦合不清楚的情況。這也是這一塊的語法很亂,對初學者不友好的原因。
2. python非同步中aiohttp獲取不到正確的Set-cookies值
python非同步中aiohttp獲取不到正確的Set-cookies值
這幾天學習了python的非同步請求,想修改之前寫的代碼提高請求效率,但遇到一個包含set-cookie返回的請求無法獲取正確的cookie值
原程序關鍵代碼(單線程)(重點看print()輸出內容):
def enter_study(num, course): # 進入每個課群的每個課程記錄每個章節url
global lesson_url # 章節
header['Referer'] = re.search(r'http.*?course', qun_course_url[num][course]).group() + 's'
print(session.cookies) # 列印當前網站的cookies
print('
')
while True:
try:
request = session.get(qun_course_url[num][course], headers=header, timeout=3) # 進入課程
print(request.cookies)
print(session.cookies) # 列印當前網站的cookies
input()
break
except Exception as e:
print('進入課程學習重試中。。。')
continue
update_time(1)
update_time(2)
temp_list = []
for x in re.findall(r'/courses/YOOCS*/">', request.text):
temp_list.append('https://xueyuan.yooc.me' + x[:-2]) # 該課程有多少章節
lesson_url[num][course] = temp_
運行結果(cookies中間空白處因為涉及用戶信息所以屏蔽了):
修改後的出現問題的代碼(非同步)(重點看print()輸出內容):
async def enter_study(num, course, header): # 進入每個課群的每個課程記錄每個章節url
async with aiohttp.ClientSession(cookies=cookie) as session:
header['Referer'] = re.search(r'http.*?course', qun_data[num][2][course]).group() + 's'
#列印請求前的cookie記錄
print(session.cookie_jar.filter_cookies())
async with session.get(qun_data[num][2][course], headers=header) as html: # 進入課程
update_time(1)
update_time(2)
print('
')
print(html.cookies)#列印Set-cookie信息
print()
#列印請求後的cookie記錄
print(print(session.cookie_jar.filter_cookies()))
if str(html.cookies).find('Set-Cookie') > -1:
save_cookie_record['Set-Cookie'] = html.cookies
rep_text = await html.text(encoding='utf-8')
temp_list = []
for x in re.findall(r'/courses/YOOCS*/">', rep_text):
temp_list.append('https://xueyuan.yooc.me' + x[:-2]) # 該課程有多少章節
qun_data[num][2][course] = temp_
運行結果(請求後返回的set-cookies信息沒有更新):
該程序是用一個cookie字典來保存cookie值的,需要通過請求後的set-cookie值來更新我的cookie字典,為什麼用在非同步請求上卻不行呢?
3. python執行多進程時,如何獲取函數返回的值
共享變數的方法。
4. 如何用Python判斷一個標識符word是不是保留字
方法1: 在IDLE里輸入這個詞,如果是保留字,則保留字會變橙色,給保留字賦值運行後也會報錯
方法2:
import keyword
keyword.kwlist
輸入結果:
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
這些都是保留字
5. python里怎麼實現多個協程一起執行,只要完
需要使用新的函數as_completed()來實現,可以把多個並發的協程一起給它,但它把返回的結果變成一個生成器,每次返回一個協程的結果,與函數wait()一樣,執行協程是亂序的,不會等所有協程執行完成才返回。例子:
importasyncio
asyncdefphase(i):
print('inphase{}'.format(i))
awaitasyncio.sleep(0.5-(0.1*i))
print('donewithphase{}'.format(i))
return'phase{}result'.format(i)
asyncdefmain(num_phases):
print('startingmain')
phases=[
phase(i)
foriinrange(num_phases)
]
print('waitingforphasestocomplete')
results=[]
fornext_to_completeinasyncio.as_completed(phases):
answer=awaitnext_to_complete
print('receivedanswer{!r}'.format(answer))
results.append(answer)
print('results:{!r}'.format(results))
returnresults
event_loop=asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(3))
finally:
event_loop.close()
結果輸出如下:starting main
waiting for phases to complete
in phase 2
in phase 1
in phase 0
done with phase 2
received answer 'phase 2 result'
done with phase 1
received answer 'phase 1 result'
done with phase 0
received answer 'phase 0 result'
results: ['phase 2 result', 'phase 1 result', 'phase 0 result']
6. python里並發執行協程時部分阻塞超時怎麼辦
碰到這種需求時不要驚慌,可以使用wait()里的timeout參數來設置等待時間,也就是從這個函數開始運行算起,如果時間到達協程沒有執行完成,就可以不再等它們了,直接從wait()函數里返回,返回之後就可以判斷那些沒有執行成功的,可以把這些協程取消掉。例子如下
importasyncio
asyncdefphase(i):
print('inphase{}'.format(i))
try:
awaitasyncio.sleep(0.1*i)
exceptasyncio.CancelledError:
print('phase{}canceled'.format(i))
raise
else:
print('donewithphase{}'.format(i))
return'phase{}result'.format(i)
asyncdefmain(num_phases):
print('startingmain')
phases=[
phase(i)
foriinrange(num_phases)
]
print('waiting0.1forphasestocomplete')
completed,pending=awaitasyncio.wait(phases,timeout=0.1)
print('{}completedand{}pending'.format(
len(completed),len(pending),
))
#
#asweexitwithoutfinishingthem.
ifpending:
print('cancelingtasks')
fortinpending:
t.cancel()
print('exitingmain')
event_loop=asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(3))
finally:
event_loop.close()
結果輸出如下:
starting main
waiting 0.1 for phases to complete
in phase 0
in phase 2
in phase 1
done with phase 0
1 completed and 2 pending
canceling tasks
exiting main
phase 1 canceled
phase 2 canceled
7. python能多核並行嗎
可以的,使用多進程就行
importmultiprocessingasmp
importtime
deffoo_pool(x):
time.sleep(2)
returnx*x
result_list=[]
deflog_result(result):
#Thisiscalledwheneverfoo_pool(i)returnsaresult.
#result_,notthepoolworkers.
result_list.append(result)
defapply_async_with_callback():
pool=mp.Pool()
foriinrange(10):
pool.apply_async(foo_pool,args=(i,),callback=log_result)
pool.close()
pool.join()
print(result_list)
if__name__=='__main__':
apply_async_with_callback()
8. python里協程事件循環里怎麼樣調用非協程函數
為了管理協程和I/O的回調函數,asyncio庫的事件循環也能基於定時的方式調用普通的函數,使用call_soon()函數,例子如下:
importasyncio
importfunctools
defcallback(arg,*,kwarg='default'):
print('callbackinvokedwith{}and{}'.format(arg,kwarg))
asyncdefmain(loop):
print('registeringcallbacks')
loop.call_soon(callback,1)
wrapped=functools.partial(callback,kwarg='notdefault')
loop.call_soon(wrapped,2)
awaitasyncio.sleep(0.1)
event_loop=asyncio.get_event_loop()
try:
print('enteringeventloop')
event_loop.run_until_complete(main(event_loop))
finally:
print('closingeventloop')
event_loop.close()
結果輸出如下:
entering event loop
registering callbacks
callback invoked with 1 and default
callback invoked with 2 and not default
closing event loop
9. python非同步有哪些方式
yield相當於return,他將相應的值返回給調用next()或者send()的調用者,從而交出了CPU使用權,而當調用者再次調用next()或者send()的時候,又會返回到yield中斷的地方,如果send有參數,還會將參數返回給yield賦值的變數,如果沒有就和next()一樣賦值為None。但是這里會遇到一個問題,就是嵌套使用generator時外層的generator需要寫大量代碼,看如下示例:
注意以下代碼均在Python3.6上運行調試
#!/usr/bin/env python# encoding:utf-8def inner_generator():
i = 0
while True:
i = yield i if i > 10: raise StopIterationdef outer_generator():
print("do something before yield")
from_inner = 0
from_outer = 1
g = inner_generator()
g.send(None) while 1: try:
from_inner = g.send(from_outer)
from_outer = yield from_inner except StopIteration: breakdef main():
g = outer_generator()
g.send(None)
i = 0
while 1: try:
i = g.send(i + 1)
print(i) except StopIteration: breakif __name__ == '__main__':
main()041
為了簡化,在Python3.3中引入了yield from
yield from
使用yield from有兩個好處,
1、可以將main中send的參數一直返回給最里層的generator,
2、同時我們也不需要再使用while循環和send (), next()來進行迭代。
我們可以將上邊的代碼修改如下:
def inner_generator():
i = 0
while True:
i = yield i if i > 10: raise StopIterationdef outer_generator():
print("do something before coroutine start") yield from inner_generator()def main():
g = outer_generator()
g.send(None)
i = 0
while 1: try:
i = g.send(i + 1)
print(i) except StopIteration: breakif __name__ == '__main__':
main()
執行結果如下:
do something before coroutine start123456789101234567891011
這里inner_generator()中執行的代碼片段我們實際就可以認為是協程,所以總的來說邏輯圖如下:
我們都知道Python由於GIL(Global Interpreter Lock)原因,其線程效率並不高,並且在*nix系統中,創建線程的開銷並不比進程小,因此在並發操作時,多線程的效率還是受到了很大制約的。所以後來人們發現通過yield來中斷代碼片段的執行,同時交出了cpu的使用權,於是協程的概念產生了。在Python3.4正式引入了協程的概念,代碼示例如下:
import asyncio# Borrowed from http://curio.readthedocs.org/en/latest/[email protected] countdown(number, n):
while n > 0:
print('T-minus', n, '({})'.format(number)) yield from asyncio.sleep(1)
n -= 1loop = asyncio.get_event_loop()
tasks = [
asyncio.ensure_future(countdown("A", 2)),
asyncio.ensure_future(countdown("B", 3))]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()12345678910111213141516
示例顯示了在Python3.4引入兩個重要概念協程和事件循環,
通過修飾符@asyncio.coroutine定義了一個協程,而通過event loop來執行tasks中所有的協程任務。之後在Python3.5引入了新的async & await語法,從而有了原生協程的概念。
async & await
在Python3.5中,引入了aync&await 語法結構,通過」aync def」可以定義一個協程代碼片段,作用類似於Python3.4中的@asyncio.coroutine修飾符,而await則相當於」yield from」。
先來看一段代碼,這個是我剛開始使用async&await語法時,寫的一段小程序。
#!/usr/bin/env python# encoding:utf-8import asyncioimport requestsimport time
async def wait_download(url):
response = await requets.get(url)
print("get {} response complete.".format(url))
async def main():
start = time.time()
await asyncio.wait([
wait_download("http://www.163.com"),
wait_download("http://www.mi.com"),
wait_download("http://www.google.com")])
end = time.time()
print("Complete in {} seconds".format(end - start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
這里會收到這樣的報錯:
Task exception was never retrieved
future: <Task finished coro=<wait_download() done, defined at asynctest.py:9> exception=TypeError("object Response can't be used in 'await' expression",)>
Traceback (most recent call last):
File "asynctest.py", line 10, in wait_download
data = await requests.get(url)
TypeError: object Response can't be used in 'await' expression123456
這是由於requests.get()函數返回的Response對象不能用於await表達式,可是如果不能用於await,還怎麼樣來實現非同步呢?
原來Python的await表達式是類似於」yield from」的東西,但是await會去做參數檢查,它要求await表達式中的對象必須是awaitable的,那啥是awaitable呢? awaitable對象必須滿足如下條件中其中之一:
1、A native coroutine object returned from a native coroutine function .
原生協程對象
2、A generator-based coroutine object returned from a function decorated with types.coroutine() .
types.coroutine()修飾的基於生成器的協程對象,注意不是Python3.4中asyncio.coroutine
3、An object with an await method returning an iterator.
實現了await method,並在其中返回了iterator的對象
根據這些條件定義,我們可以修改代碼如下:
#!/usr/bin/env python# encoding:utf-8import asyncioimport requestsimport time
async def download(url): # 通過async def定義的函數是原生的協程對象
response = requests.get(url)
print(response.text)
async def wait_download(url):
await download(url) # 這里download(url)就是一個原生的協程對象
print("get {} data complete.".format(url))
async def main():
start = time.time()
await asyncio.wait([
wait_download("http://www.163.com"),
wait_download("http://www.mi.com"),
wait_download("http://www.google.com")])
end = time.time()
print("Complete in {} seconds".format(end - start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())27282930
好了現在一個真正的實現了非同步編程的小程序終於誕生了。
而目前更牛逼的非同步是使用uvloop或者pyuv,這兩個最新的Python庫都是libuv實現的,可以提供更加高效的event loop。
uvloop和pyuv
pyuv實現了Python2.x和3.x,但是該項目在github上已經許久沒有更新了,不知道是否還有人在維護。
uvloop只實現了3.x, 但是該項目在github上始終活躍。
它們的使用也非常簡單,以uvloop為例,只需要添加以下代碼就可以了
import asyncioimport uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())123
10. Python怎麼多線程中添加協程
由於python是一種解釋性腳本語言,python的多線程在運行過程中始終存在全局線程鎖。
簡單的來說就是在實際的運行過程中,python只能利用一個線程,因此python的多線程並不達到C語言多線程的性能。
可以使用多進程來代替多線程,但需要注意的是多進程最好不要涉及到例如文件操作的頻繁操作IO的功能。