Python 裝飾器裝飾類中的方法這篇文章,使用了裝飾器來捕獲代碼異常。這種方式可讓代碼變得更加簡潔和Pythonic。 在寫代碼的過程當中,處理異常並重試是一個很是常見的需求。可是如何把捕獲異常並重試寫得簡潔高效,這就是一個技術活了。 以爬蟲開發爲例,因爲網頁返回的源代碼有各類不一樣的狀況,所以捕獲異常並重試是很常見的要求。下面這幾段代碼是我多年之前,在剛開始學習爬蟲的時候,因爲捕獲異常並重試致使代碼混亂化過程。 代碼一開始的邏輯很是簡單,獲取網頁後臺API返回的JSON字符串,轉化成字典,提取出裏面data的數據,而後傳遞給save()函數: def extract(url): info_json = requests.get(url).content.decode() info_dict = json.loads(info_json) data = info_dict['data'] save(data) 代碼運行一段時間,發現有時候JSON會隨機出現解析錯誤。因而添加捕獲異常並重試的功能: def extract(url): info_json = requests.get(url).text try: info_dict = json.loads(info_json) except Exception: print('網頁返回的不是有效的JSON格式字符串,重試!') extract(url) return data = info_dict['data'] save(data) 後來又發現,有部份的URL會致使遞歸深度超過最大值。這是由於有一些URL返回的是數據始終是錯誤的,而有些URL,重試幾回又能返回正常的JSON數據,因而限制只重試3次: def extract(url): info_json = requests.get(url).text try: info_dict = json.loads(info_json) except Exception: print('網頁返回的不是有效的JSON格式字符串,重試!') for i in range(3): if extract(url): break data = info_dict['data'] save(data) return True 後來又發現,不能馬上重試,重試要有時間間隔,而且時間間隔逐次增大…… 從上面的例子中能夠看到,對於異常的捕獲和處理,一不當心就讓整個代碼變得很難看很難維護。爲了解決這個問題,就須要經過裝飾器來完成處理異常並重試的功能。 Python 有一個第三方庫,叫作Tenacity,它實現了一種優雅的重試功能。 以上面爬蟲最初的無限重試版本爲例,若是想實現遇到異常就重試。只須要添加兩行代碼,爬蟲的主體函數徹底不須要作修改: from tenacity import retry @retry def extract(url): info_json = requests.get(url).content.decode() info_dict = json.loads(info_json) data = info_dict['data'] save(data) 如今要限制重試次數爲3次,代碼總行數不須要新增一行就能實現: from tenacity import retry, stop_after_attempt @retry(stop=stop_after_attempt(3)) def extract(url): info_json = requests.get(url).content.decode() info_dict = json.loads(info_json) data = info_dict['data'] save(data) 如今想每5秒鐘重試一次,代碼行數也不須要增長: from tenacity import retry, wait_fixed @retry(wait=wait_fixed(5)) def extract(url): info_json = requests.get(url).content.decode() info_dict = json.loads(info_json) data = info_dict['data'] save(data) 甚至重試的時間間隔想指數級遞增,代碼行數也不須要增長: from tenacity import retry, wait_exponential @retry(wait=wait_exponential(multiplier=1, max=10)) # 重試時間間隔知足:2^n * multiplier, n爲重試次數,但最多間隔10秒 def extract(url): info_json = requests.get(url).content.decode() info_dict = json.loads(info_json) data = info_dict['data'] save(data) 重試不只能夠限制次數和間隔時間,還能夠針對特定的異常進行重試。在爬蟲主體中,其實有三個地方可能出現異常: requests獲取網頁出錯 解析JSON出錯 info_dict字典裏面沒有data這個key 若是隻須要在JSON解析錯誤時重試,因爲異常類型爲json.decoder.JSONDecodeError,因此就能夠經過參數來進行限制: from tenacity import retry, retry_if_exception_type from json.decoder import JSONDecodeError @retry(retry=retry_if_exception_type(JSONDecodeError)) def extract(url): info_json = requests.get(url).content.decode() info_dict = json.loads(info_json) data = info_dict['data'] save(data) 固然,這些特性均可以進行組合,例如只對JSONDecodeError 進行重試,每次間隔5秒,重試三次,那就寫成: from tenacity import retry, retry_if_exception_type, wait_fixed, stop_after_attempt from json.decoder import JSONDecodeError @retry(retry=retry_if_exception_type(JSONDecodeError), wait=wait_fixed(5), stop=stop_after_attempt(3)) def extract(url): info_json = requests.get(url).content.decode() info_dict = json.loads(info_json) data = info_dict['data'] save(data) 自始至終,爬蟲主體的代碼徹底不須要作任何修改。 Tenacity是我見過的,最 Pythonic ,最優雅的第三方庫。
原文連接:https://kingname.info/2017/06/18/easy-retry/