чи існує пітонічний спосіб спробувати щось максимум разів? [дублікат]


85

У мене є сценарій python, який запитує сервер MySQL на загальному хості Linux. З якоїсь причини запити до 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])

Зрозуміло, я міг би зробити це, зробивши ще одну спробу в пункті за винятком, але це неймовірно потворно, і я відчуваю, що для цього потрібно мати гідний спосіб.


2
Це хороший момент. Я, мабуть, заснув на кілька секунд. Я не знаю, що не так з установкою MySQL на сервері, але, здається, вона не працює одну секунду, а наступної працює.
Бен

3
@Yuval A: Це загальне завдання. Я підозрюю, що це навіть вбудовано в Ерланг.
jfs

1
Тільки для того, щоб згадати, що, можливо, нічого страшного, Mysql має змінну wait_timeout, щоб налаштувати mysql на відключення неактивних з'єднань.
andy

Відповіді:


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
Ну, мені начебто подобається моя, оскільки в ній чітко видно, що спроби збільшуються лише у випадку винятку.
Дана

2
Так, мабуть, я більше параноїк щодо нескінченних whileпетель, що закрадаються, ніж більшість людей.
cdleary

5
-1: Не люблю перерву. Як "поки не зроблено і спроби <3:" краще.
S.Lott

5
Мені подобається перерва, але не час. Це більше схоже на C-ish, ніж на пітонічне. бо я в діапазоні краще імхо.
hasen

78

Спираючись на відповідь Дани, ви можете зробити це як декоратор:

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
Дана

Це має бути "return func () у блоці try, а не просто" func () ".
Роберт Росней,

Бах! Дякую за голови.
dwc

Ви насправді спробували запустити це? Це не працює. Проблема полягає в тому, що виклик func () у функції tryIt виконується, як тільки ви прикрашаєте функцію, а не тоді, коли ви насправді викликаєте декоровану функцію. Вам потрібна ще одна вкладена функція.
Стів Лош,

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
Бібліотека повторних спроб замінена бібліотекою стійкості .
Сет

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: ще і перерва ... icky. Віддайте перевагу більш чіткому "поки не зроблено і підрахуйте! = Спроба_рахувати", ніж перерві.
S.Lott,

1
Справді? Я думав, що це має більше сенсу таким чином - якщо виняток не відбувається, вирвіться з циклу. Я можу надмірно боятися нескінченних петель.
cdleary

4
+1: Я ненавиджу змінні прапорів, коли мова включає структури коду, щоб зробити це за вас. Для отримання бонусних балів поставте інше на, щоб мати справу з невдалими спробами.
xorsyst

6

Як і С.Лотт, мені подобається прапор, щоб перевірити, чи ми закінчили:

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.