HOWTO · Python

Python デコレータを使用してコード ブロックを再試行する

このチュートリアルでは、デコレーターを使用して、Python で特定の例外や条件が発生したときにコード ブロックを再試行する方法について説明します。

このページの内容

関数またはクラスをデコレータで変更して、関数の動作を永続的に変更することなく拡張できます。 この記事では、retry デコレーターを使用して、既存の関数を変更せずに既存の関数を変更する方法について説明します。

この場合、変更により、戻り値が必要なものと異なる可能性がある特定の状況で、関数が数回再試行されます。

retry デコレーターの重要性

デコレータを使用して特定の関数の動作を拡張できます。また、アクセス権がない場合や変更したくない場合でも、デコレータを簡単に作成してその関数を変更できます。

この関数はかなり特殊な方法で必要になることがよくあります。そこで Python デコレータの出番です。では、デコレータがどのように機能するかを示す簡単な関数を作成しましょう。

単純な関数 quotient() は 2つの引数を取り、最初の引数を 2 番目の引数で割ります。

def quotient(a, b):
    return a / b


print(quotient(3, 7))

出力:

0.42857142857142855

ただし、除算の結果を常に大きい方が除算されるようにしたい場合 (したがって、結果は 2.3333333333333335 になります)、コードを変更するか、decorators を使用することができます。

デコレータを使用すると、コード ブロックを変更せずに関数の動作を拡張できます。

def improv(func):
    def inner(a, b):
        if a < b:
            a, b = b, a
        return func(a, b)

    return inner


@improv
def quotient(a, b):
    return a / b


print(quotient(3, 7))

出力:

2.3333333333333335

improv() 関数は、quotient() 関数を引数として取り、quotient() 関数の引数を取り、必要な追加機能をもたらす内部関数を保持するデコレーター関数です。 追加。

現在、デコレータを使用して、特定の関数、特にアクセスできない関数に retry 機能を追加できます。

retry デコレータは、予測できない動作やエラーが発生する可能性があり、それらが発生したときに同じ操作を再試行するシナリオで役立ちます。

典型的な例は、for ループ内で失敗したリクエストを処理することです。 そのようなシナリオでは、retry デコレータを使用して、その特定のリクエストを指定回数だけ再試行することを管理できます。

@retry を使用して Python でコード ブロックを再試行する

retry デコレータには、この機能を提供するさまざまなライブラリがあり、そのようなライブラリの 1つが retrying ライブラリ です。

これにより、wait および stop 条件を Exceptions から期待される戻り値まで指定できます。 retrying ライブラリをインストールするには、次のように pip コマンドを使用できます。

pip install retrying

それでは、0 と 10 の間の数字をランダムに作成し、数字が 1 より大きい場合に ValueError を発生させる関数を作成しましょう。

import random


def generateRandomly():
    if random.randint(0, 10) > 1:
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

その時点で生成された数値が 1 より大きい場合、コード出力は次のようになります。

Traceback (most recent call last):
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 11, in <module>
    print(generateRandomly())
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 6, in generateRandomly
    raise ValueError("Number generated is greater than one")
ValueError: Number generated is greater than one

コードに ValueError がスローされる状況はあり得ないので、retry デコレーターを導入して、ValueError が発生しなくなるまで generateRandomly() 関数を再試行できます。

import random
from retrying import retry


@retry
def generateRandomly():
    if random.randint(0, 10) > 1:
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

出力:

Finally Generated.

ここで、retry デコレータは、ValueError がなくなるまで random 操作を再試行し、文字列 Finally Generated. のみが得られます。

if ブロック内に print() ステートメントを導入することで、コードが generateRandomly() を再試行した回数を確認できます。

import random
from retrying import retry


@retry
def generateRandomly():
    if random.randint(0, 10) > 1:
        print("1")
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

出力:

1
1
1
1
1
1
1
Finally Generated.

ここでは 8 ですが、コードを実行すると異なる場合があります。 ただし、コードが長時間再試行し続ける状況はあり得ません。 したがって、stop_max_attempt_number や stop_max_delay などの引数があります。

import random
from retrying import retry


@retry(stop_max_attempt_number=5)
def generateRandomly():
    if random.randint(0, 10) > 1:
        print("1")
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

出力:

1
1
1
Finally Generated.

関数は 5 回だけ再試行されますが、5 回目より前に成功するか、値 Finally Generated. を返すか、失敗して ValueError をスローします。

1
1
1
1
1
File "C:\Python310\lib\site-packages\retrying.py", line 247, in get
    six.reraise(self.value[0], self.value[1], self.value[2])
  File "C:\Python310\lib\site-packages\six.py", line 719, in reraise
    raise value
  File "C:\Python310\lib\site-packages\retrying.py", line 200, in call
    attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 9, in generateRandomly
    raise ValueError("Number generated is greater than one")
ValueError: Number generated is greater than one

tenacity を使用して Python でコード ブロックを再試行する

retrying ライブラリは少し変わっている可能性があり、もはや維持されていませんが、tenacity ライブラリ はそのすべての機能に使い捨てのより多くのツールを提供します。

tenacity をインストールするには、次の pip コマンドを使用します。

pip install tenacity

3 で stop 試行を使用して同じコードを試すことができます。

import random
from tenacity import retry, stop_after_attempt


@retry(stop=stop_after_attempt(3))
def generateRandomly():
    if random.randint(0, 10) > 1:
        print("1")
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

3 回の試行時間枠内で、生成された乱数が 1 未満の場合のコードの出力。

1
Finally Generated.

ただし、そうでない場合は、以下の出力がスローされます。

1
1
1
Traceback (most recent call last):
  File "C:\Python310\lib\site-packages\tenacity\__init__.py", line 407, in __call__
    result = fn(*args, **kwargs)
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 9, in generateRandomly
    raise ValueError("Number generated is greater than one")
ValueError: Number generated is greater than one

The above exception was a direct cause of the following exception:

Traceback (most recent call last):
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 14, in <module>
    print(generateRandomly())
  File "C:\Python310\lib\site-packages\tenacity\__init__.py", line 324, in wrapped_f
    return self(f, *args, **kw)
  File "C:\Python310\lib\site-packages\tenacity\__init__.py", line 404, in __call__
    do = self.iter(retry_state=retry_state)
  File "C:\Python310\lib\site-packages\tenacity\__init__.py", line 361, in iter
    raise retry_exc from fut.exception()
tenacity.RetryError: RetryError[<Future at 0x29a75442c20 state=finished raised ValueError>]