最大回数まで何かを試すためのPythonの方法はありますか?[複製]


85

共有Linuxホスト上のMySQLサーバーにクエリを実行するPythonスクリプトがあります。何らかの理由で、MySQLへのクエリはしばしば「サーバーがなくなった」エラーを返します。

_mysql_exceptions.OperationalError: (2006, 'MySQL server has gone away')

直後にクエリを再試行すると、通常は成功します。だから、私はPythonでクエリを実行しようとする賢明な方法があるかどうかを知りたいのですが、失敗した場合は、固定回数まで再試行します。たぶん、完全に諦める前に5回試してみたいと思います。

これが私が持っている種類のコードです:

conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()

try:
    cursor.execute(query)
    rows = cursor.fetchall()
    for row in rows:
        # do something with the data
except MySQLdb.Error, e:
    print "MySQL Error %d: %s" % (e.args[0], e.args[1])

明らかに、except節で別の試みをすることでそれを行うことができましたが、それは信じられないほど醜いものであり、これを達成するための適切な方法があるに違いないと感じています。


2
それは良い点です。私はおそらく数秒間眠りにつくでしょう。サーバーへのMySQLのインストールの何が問題になっているのかわかりませんが、1秒間失敗し、次は機能するようです。
ベン

3
@Yuval A:それは一般的なタスクです。Erlangにも組み込まれているのではないかと思います。
jfs 2009

1
おそらく何も問題がないことを言及するために、Mysqlには非アクティブな接続をドロップするようにmysqlを構成するためのwait_timeout変数があります。
andy 2016年

回答:


97

どうですか:

conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()
attempts = 0

while attempts < 3:
    try:
        cursor.execute(query)
        rows = cursor.fetchall()
        for row in rows:
            # do something with the data
        break
    except MySQLdb.Error, e:
        attempts += 1
        print "MySQL Error %d: %s" % (e.args[0], e.args[1])

19
またはfor attempt_number in range(3)
cdleary 2009

8
ええと、例外が発生した場合にのみ試行が増加することを明示しているので、私はちょっと私のようです。
ダナ

2
ええ、私whileはほとんどの人よりも無限ループが忍び寄るのを妄想していると思います。
cdleary 2009

5
-1:休憩が嫌い。「行われていない間、<3:」のように。
S.Lott 2009

5
私は休憩が好きですが、しばらくは好きではありません。これは、pythonicというよりC-ishに似ています。範囲内の私はより良い私見です。
hasen 2009

78

Danaの答えに基づいて、デコレータとしてこれを実行することをお勧めします。

def retry(howmany):
    def tryIt(func):
        def f():
            attempts = 0
            while attempts < howmany:
                try:
                    return func()
                except:
                    attempts += 1
        return f
    return tryIt

次に...

@retry(5)
def the_db_func():
    # [...]

decoratorモジュールを使用する拡張バージョン

import decorator, time

def retry(howmany, *exception_types, **kwargs):
    timeout = kwargs.get('timeout', 0.0) # seconds
    @decorator.decorator
    def tryIt(func, *fargs, **fkwargs):
        for _ in xrange(howmany):
            try: return func(*fargs, **fkwargs)
            except exception_types or Exception:
                if timeout is not None: time.sleep(timeout)
    return tryIt

次に...

@retry(5, MySQLdb.Error, timeout=0.5)
def the_db_func():
    # [...]

モジュールをインストールするにdecorator

$ easy_install decorator

2
デコレータはおそらく例外クラスも取得する必要があるため、以下を除いてベアを使用する必要はありません。すなわち@retry(5、MySQLdb.Error)
cdleary 2009

気の利いた!デコレータを使うとは思わない:P
Dana

。それは、FUNC() 『「tryブロックだけでなく、中に戻りFUNC()』である必要があります
ロバートRossney

ああ!ヘッドアップをありがとう。
dwc 2009

実際にこれを実行してみましたか?動作しません。問題は、tryIt関数のfunc()呼び出しが、実際に装飾された関数を呼び出すときではなく、関数を装飾するとすぐに実行されることです。別の入れ子関数が必要です。
Steve Losh

12

更新:テナシティと呼ばれる再試行ライブラリのより適切に保守されたフォークがありますこれは、より多くの機能をサポートし、一般により柔軟性があります。


はい、再試行ライブラリがあります。このライブラリには、組み合わせることができるいくつかの種類の再試行ロジックを実装するデコレータがあります。

いくつかの例:

@retry(stop_max_attempt_number=7)
def stop_after_7_attempts():
    print "Stopping after 7 attempts"

@retry(wait_fixed=2000)
def wait_2_s():
    print "Wait 2 second between retries"

@retry(wait_exponential_multiplier=1000, wait_exponential_max=10000)
def wait_exponential_1000():
    print "Wait 2^x * 1000 milliseconds between each retry,"
    print "up to 10 seconds, then 10 seconds afterwards"

2
再試行ライブラリは、粘り強さライブラリに置き換えられました。
セス

8
conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()

for i in range(3):
    try:
        cursor.execute(query)
        rows = cursor.fetchall()
        for row in rows:
            # do something with the data
        break
    except MySQLdb.Error, e:
        print "MySQL Error %d: %s" % (e.args[0], e.args[1])

1
あなたは追加することができ、他の下部にある:else: raise TooManyRetriesCustomException
ボブ・スタイン

6

私はそれを次のようにリファクタリングします:

def callee(cursor):
    cursor.execute(query)
    rows = cursor.fetchall()
    for row in rows:
        # do something with the data

def caller(attempt_count=3, wait_interval=20):
    """:param wait_interval: In seconds."""
    conn = MySQLdb.connect(host, user, password, database)
    cursor = conn.cursor()
    for attempt_number in range(attempt_count):
        try:
            callee(cursor)
        except MySQLdb.Error, e:
            logging.warn("MySQL Error %d: %s", e.args[0], e.args[1])
            time.sleep(wait_interval)
        else:
            break

callee関数を因数分解すると機能が分割されるため、再試行コードにとらわれることなくビジネスロジックを簡単に確認できます。


-1:else and break ...厄介です。ブレークよりも明確な「実行されていない間、カウント!= attempt_count」を優先します。
S.Lott 2009

1
本当に?この方法の方が理にかなっていると思いました。例外が発生しない場合は、ループから抜け出します。私は無限のwhileループを過度に恐れているかもしれません。
cdleary 2009

4
+1:言語にそれを行うためのコード構造が含まれている場合、フラグ変数は嫌いです。ボーナスポイントについては、すべての試行の失敗に対処するためにforにelseを付けてください。
xorsyst 2011年

6

S.Lottのように、完了したかどうかを確認するためのフラグが好きです。

conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()

success = False
attempts = 0

while attempts < 3 and not success:
    try:
        cursor.execute(query)
        rows = cursor.fetchall()
        for row in rows:
            # do something with the data
        success = True 
    except MySQLdb.Error, e:
        print "MySQL Error %d: %s" % (e.args[0], e.args[1])
        attempts += 1

1
def successful_transaction(transaction):
    try:
        transaction()
        return True
    except SQL...:
        return False

succeeded = any(successful_transaction(transaction)
                for transaction in repeat(transaction, 3))

1

1.定義:

def try_three_times(express):
    att = 0
    while att < 3:
        try: return express()
        except: att += 1
    else: return u"FAILED"

2.使用法:

try_three_times(lambda: do_some_function_or_express())

HTMLコンテキストの解析に使用します。


0

これは私の一般的な解決策です:

class TryTimes(object):
    ''' A context-managed coroutine that returns True until a number of tries have been reached. '''

    def __init__(self, times):
        ''' times: Number of retries before failing. '''
        self.times = times
        self.count = 0

    def __next__(self):
        ''' A generator expression that counts up to times. '''
        while self.count < self.times:
            self.count += 1
        yield False

    def __call__(self, *args, **kwargs):
        ''' This allows "o() calls for "o = TryTimes(3)". '''
        return self.__next__().next()

    def __enter__(self):
        ''' Context manager entry, bound to t in "with TryTimes(3) as t" '''
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        ''' Context manager exit. '''
        return False # don't suppress exception

これにより、次のようなコードが可能になります。

with TryTimes(3) as t:
    while t():
        print "Your code to try several times"

また可能:

t = TryTimes(3)
while t():
    print "Your code to try several times"

これは、より直感的な方法で例外を処理することで改善できると思います。提案を受け入れます。


0

最大の効果を得るためにforelse句付きのループを使用できます。

conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()

for n in range(3):
    try:
        cursor.execute(query)
    except MySQLdb.Error, e:
        print "MySQL Error %d: %s" % (e.args[0], e.args[1])
    else:
        rows = cursor.fetchall()
        for row in rows:
            # do something with the data
        break
else:
    # All attempts failed, raise a real error or whatever

重要なのは、クエリが成功するとすぐにループから抜け出すことです。このelse句は、ループがbreak。なしで完了した場合にのみトリガーされます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.