최대 횟수까지 무언가를 시도하는 비단뱀적인 방법이 있습니까? [복제]


85

공유 리눅스 호스트에서 MySQL 서버를 쿼리하는 파이썬 스크립트가 있습니다. 어떤 이유로 MySQL에 대한 쿼리는 종종 "server has gone away"오류를 반환합니다.

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

나중에 즉시 쿼리를 다시 시도하면 일반적으로 성공합니다. 그래서 저는 파이썬에서 쿼리를 실행하는 합리적인 방법이 있는지 알고 싶습니다. 실패하면 고정 된 횟수까지 다시 시도합니다. 아마 포기하기 전에 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 초 동안 실패한 것 같고 다음에는 작동합니다.
Ben

3
@Yuval A : 일반적인 작업입니다. Erlang에 내장되어 있다고 생각합니다.
jfs

1
잘못된 것이 없을 수도 있음을 언급하기 위해 Mysql에는 비활성 연결을 삭제하도록 mysql을 구성 하는 wait_timeout 변수가 있습니다.
앤디

답변:


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

8
글쎄, 나는 예외가 발생했을 때만 시도가 증가한다는 것을 명시하기 때문에 내 것이 좋습니다.
Dana

2
그래, 나는 while대부분의 사람들보다 무한 루프 에 대해 더 편집증 적이라고 생각한다 .
cdleary

5
-1 : 휴식을 좋아하지 않습니다. "완료되지 않은 상태에서 <3 :"시도가 더 좋습니다.
S.Lott

5
나는 휴식을 좋아하지만 그 동안은 그렇지 않습니다. 이것은 파이썬보다 C-ish와 더 비슷합니다. 나는 범위 내에서 더 나은 imho입니다.
hasen

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

맵시 있는! 내가 사용하는 장식으로 생각하지 : P
다나

그것은해야한다 "try 블록에서 반환 FUNC ()이 아니라"FUNC (). "
로버트 Rossney

Bah! 알려 주셔서 감사합니다.
dwc

실제로 이것을 실행 해 보았습니까? 작동하지 않습니다. 문제는 tryIt 함수의 func () 호출 이 실제로 데코 레이팅 된 함수를 호출 할 때가 아니라 함수 를 데코레이션하자마자 실행된다는 것입니다. 다른 중첩 함수가 필요합니다.
Steve Losh

12

업데이트 : 더 많은 기능을 지원하고 일반적으로 더 유연한 재시도 라이브러리 인 tenacity 라는 더 나은 유지 관리 포크가 있습니다.


예, 결합 할 수있는 여러 종류의 재시도 로직을 구현하는 데코레이터 가있는 재시도 라이브러리 가 있습니다.

몇 가지 예 :

@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
재시도 라이브러리가 끈기 라이브러리 로 대체되었습니다 .
Seth

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
Bob Stein

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 : 그렇지 않으면 휴식 ... icky. break보다 더 명확한 "while not done and count! = attempt_count"를 선호합니다.
S.Lott

1
정말? 이 방법이 더 합리적이라고 생각했습니다. 예외가 발생하지 않으면 루프에서 벗어나십시오. 무한 while 루프를 지나치게 두려워 할 수 있습니다.
cdleary

4
+1 : 언어에 코드 구조가 포함되어있는 경우 플래그 변수가 싫습니다. 보너스 포인트의 경우 모든 시도가 실패한 경우를 처리하기 위해 for에 다른 것을 입력하십시오.
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

최대 효과를 위해 절 for과 함께 루프를 사용할 수 있습니다 else.

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.