LoginSignup
0
3

Pythonの再試行処理を関数化する

Last updated at Posted at 2021-10-24

『たまに失敗するが、その場合は成功するまで再試行させたい』という処理がある

  • ネットワークに接続する処理
  • 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)
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3