流暢的python示例:異步下載國旗圖片

異步下載國旗圖片和國家名數據

import aiohttp
import asyncio
import os
import time
import sys

POP20_CC = ('CN IN US ID BR PK NG BD RU JP MX PH VN ET EG DE IR CD FR').split()
BASE_URL = 'http://flupy.org/data/flags'
DEST_DIR = 'downloads/'

class FetchError(Exception):  #用來捕獲異常
    def __init__(self, country_code):
        self.country_code = country_code

def save_flag(img, filename):  #保存圖片文件
    path = os.path.join(DEST_DIR, filename)
    with open(path, 'wb') as fp:
        fp.write(img)

async def http_get(session, url):  #負責下載的主函數,session由download_many傳遞
    async with session.get(url) as resp:
        if resp.status == 200:
            ctype = resp.headers.get('Content-type', '').lower() 
            if 'json' in ctype or url.endswith('.json'): #國家名是json數據。若是內容類型是json
                data = await resp.json()  #那麼就用json()方法獲取內容
            else:
                data = await resp.read()  #不然直接獲取元數據
            return data
        elif resp.status == 404: #捕獲異常
            raise web.HTTPNotFound()
        else:
            raise aiohttp.errors.HttpProcessingError(code=res.sstatus, message=res.reason, headers=res.headers)

async def get_flag(session, cc):  #獲取圖片
    url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
    image = await http_get(session, url)  #這裏是I\o請求須要異步操做
    return image

async def get_country(session, cc):  #獲取國家名
    url = '{}/{cc}/metadata.json'.format(BASE_URL, cc=cc.lower())
    metadata = await http_get(session, url) #這裏是I\o請求須要異步操做
    return metadata['country']

def show(text):
    print(text + '[OK]  ', end='')
    sys.stdout.flush()  #挨個輸出國家名,沒有這個刷新緩衝區方法就會最後一會兒輸出全部國家名。

async def download_one(session, cc):
    try:
        image = await get_flag(session, cc)  #異步獲取圖片
        country = await get_country(session, cc)  #異步獲取國家名
    except web.HTTPNotFound:
        msg = 'not found'
    except Exception as exc:
        raise FetchError(cc) from exc
    else:  #try中的else子句在沒有except異常時會運行
        country = country.replace(' ', '_')
        filename = '{}-{}.gif'.format(cc, country)
        loop = asyncio.get_event_loop()  #這裏獲取循環是爲了在保存圖片時不阻塞
        loop.run_in_executor(None, save_flag, image, filename) #run_in_excutor函數內部維護了一個TheardPollExecutor對象[注1]。第一個參數默認爲concurrent.futures.Executor實例。
    show(cc)
    return cc

async def download_many(cc_list):
    async with aiohttp.ClientSession() as session:  #獲取ClientSession對象
        res = await asyncio.gather(*[asyncio.ensure_future(download_one(session, cc)) for cc in sorted(cc_list)]) #gather函數若是參數爲協程對象它會自動排定爲一個task,這裏咱們直接用的ensure_future方法生成了task對象。而後併發的運行他們,返回一個結果聚合值列表。
    return len(res)

def main():
    t0 = time.time()
    loop = asyncio.get_event_loop()
    count = loop.run_until_complete(download_many(POP20_CC))
    loop.close()
    elapsed = time.time() - t0
    msg = '\n{} flags download in {:.2f}s'
    print(msg.format(count, elapsed))

if __name__ == '__main__':
    main()

#def a(*x):
    #print(x)
#a([1,2]) --> ([1,2],)
#a(*[1,2]) --> (1,2)
#*號能夠將列表或元組裏的元素解包,每一個元素做爲單獨的參數傳入

其實,異步庫依賴於低層線程(直至內核級線程),可是這些庫的用戶無需建立線程,也無需知道用到了基礎設施中的低層線程。在應用中,咱們只需確保沒有阻塞的代碼,事件循環會在背後處理併發。異步系統能避免用戶線程級的開銷,這是它能比多線程系統管理更多併發鏈接的緣由。web

【注1】The loop.run_in_executor() method can be used with a concurrent.futures.ThreadPoolExecutor to execute blocking code in a different OS thread without blocking the OS thread that the event loop runs in.json

相關文章
相關標籤/搜索