『たまに失敗するが、その場合は成功するまで再試行させたい』という処理がある
例
- ネットワークに接続する処理
- Seleniumで要素を取得する処理
など
世の中の人々は再試行処理をどうしているのか
Googleの公式ドキュメント
import random
import time
from apiclient.errors import HttpError
def makeRequestWithExponentialBackoff(analytics):
"""Wrapper to request Google Analytics data with exponential backoff.
The makeRequest method accepts the analytics service object, makes API
requests and returns the response. If any error occurs, the makeRequest
method is retried using exponential backoff.
Args:
analytics: The analytics service object
Returns:
The API response from the makeRequest method.
"""
for n in range(0, 5):
try:
return makeRequest(analytics)
except HttpError, error:
if error.resp.reason in ['userRateLimitExceeded', 'quotaExceeded',
'internalServerError', 'backendError']:
time.sleep((2 ** n) + random.random())
else:
break
print "There has been an error, the request never succeeded."
処理が失敗する毎に待ち時間を増やして再実行していく方式のことを指数バックオフと言うらしい
ネットワークに負荷をかけないためにも、このやり方を使ってみるのがよさそう。
上記のPython2の実装例を参考に、Python3での汎用的な関数として実装し直してみた。
import random
import time
from typing import Any, Callable
def main():
try_until_success(sometime_fail)
def sometime_fail():
"""一定の確率でExceptionが発生する処理"""
n = random.random()
if n > 0.7:
print("成功!")
else:
raise Exception("失敗...")
def try_until_success(f: Callable) -> Any:
"""成功するまで試行する(最大5回)
エラーが発生した場合、待機秒数を指数関数的に増やして再試行
Args:
f (Callable): 失敗する可能性のある関数
Raises:
Exception: 5回試行して全て失敗した場合は続行不可能とみなす
Returns:
Any: fの実行に成功した際の戻り値
"""
for n in range(0, 5):
try:
result = f()
return result
except Exception as e:
print(e)
wait_time = (2 ** n) + random.random()
print("{:.1f}秒待機して再試行します...".format(wait_time))
time.sleep(wait_time)
raise Exception("5回試行しましたが、全て失敗しました...")
if __name__ == "__main__":
main()
関数で定義する程ではない処理を渡したい場合はラムダ式を使うと良い
import requests
def main():
f = lambda: requests.get("https://www.google.co.jp") # noqa: E731
res = try_until_success(f)