У меня есть сценарий 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])
Ясно, что я мог бы сделать это, выполнив еще одну попытку в разделе except, но это невероятно некрасиво, и я чувствую, что должен быть достойный способ добиться этого.
Ответы:
Как насчет:
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])
источник
for attempt_number in range(3)
while
циклов, чем большинство людей.Основываясь на ответе Даны, вы можете сделать это как декоратор:
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
модуль :источник
ОБНОВЛЕНИЕ: существует улучшенная поддерживаемая ветвь библиотеки повторных попыток, называемая упорством , которая поддерживает больше функций и в целом более гибкая.
Да, есть библиотека повторных попыток , в которой есть декоратор, реализующий несколько видов логики повторных попыток, которые вы можете комбинировать:
Несколько примеров:
@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"
источник
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])
источник
else: raise TooManyRetriesCustomException
Я бы реорганизовал его так:
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
функции за скобки, по- видимому, нарушает функциональность, так что бизнес-логику легко увидеть, не увязнув в коде повтора.источник
Как и 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
источник
def successful_transaction(transaction): try: transaction() return True except SQL...: return False succeeded = any(successful_transaction(transaction) for transaction in repeat(transaction, 3))
источник
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.
источник
Это мое общее решение:
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"
Надеюсь, это можно улучшить, обрабатывая исключения более интуитивно понятным способом. Открыт для предложений.
источник
Для максимального эффекта можно использовать
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
.источник