Mam skrypt w Pythonie, który wysyła zapytanie do serwera MySQL na współużytkowanym hoście Linux. Z jakiegoś powodu zapytania do MySQL często zwracają błąd „serwer zniknął”:
_mysql_exceptions.OperationalError: (2006, 'MySQL server has gone away')
Jeśli spróbujesz wykonać zapytanie ponownie natychmiast później, zwykle kończy się to pomyślnie. Tak więc chciałbym wiedzieć, czy w Pythonie istnieje rozsądny sposób, aby spróbować wykonać zapytanie, a jeśli się nie powiedzie, spróbować ponownie, do ustalonej liczby prób. Prawdopodobnie chciałbym spróbować 5 razy, zanim całkowicie się poddam.
Oto rodzaj kodu, który mam:
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])
Oczywiście mógłbym to zrobić, podejmując kolejną próbę w klauzuli oprócz, ale to jest niesamowicie brzydkie i mam wrażenie, że musi istnieć przyzwoity sposób na osiągnięcie tego.
Odpowiedzi:
Co powiesz na:
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])
źródło
for attempt_number in range(3)
while
pętli niż większość ludzi.Opierając się na odpowiedzi Dany, możesz zrobić to jako dekorator:
def retry(howmany): def tryIt(func): def f(): attempts = 0 while attempts < howmany: try: return func() except: attempts += 1 return f return tryIt
Następnie...
@retry(5) def the_db_func(): # [...]
Ulepszona wersja korzystająca z
decorator
modułuimport 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
Następnie...
@retry(5, MySQLdb.Error, timeout=0.5) def the_db_func(): # [...]
Aby zainstalować ten
decorator
moduł :źródło
AKTUALIZACJA: istnieje lepiej zarządzana rozwidlenie biblioteki ponawiania zwane wytrwałością , która obsługuje więcej funkcji i jest ogólnie bardziej elastyczna.
Tak, istnieje biblioteka ponawiania , która ma dekorator, który implementuje kilka rodzajów logiki ponawiania, które można łączyć:
Kilka przykładów:
@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"
źródło
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])
źródło
else: raise TooManyRetriesCustomException
Zrobiłbym to tak:
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
Rozłożenie
callee
funkcji na czynniki wydaje się przerywać funkcjonalność, dzięki czemu można łatwo zobaczyć logikę biznesową bez ugrzęźnięcia w kodzie ponawiania.źródło
Podobnie jak S.Lott, lubię flagę, aby sprawdzić, czy skończyliśmy:
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
źródło
def successful_transaction(transaction): try: transaction() return True except SQL...: return False succeeded = any(successful_transaction(transaction) for transaction in repeat(transaction, 3))
źródło
1. definicja:
def try_three_times(express): att = 0 while att < 3: try: return express() except: att += 1 else: return u"FAILED"
2. zastosowanie:
try_three_times(lambda: do_some_function_or_express())
Używam go do analizy kontekstu html.
źródło
Oto moje ogólne rozwiązanie:
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
Pozwala to na kod podobny do następującego:
with TryTimes(3) as t: while t(): print "Your code to try several times"
Również możliwe:
t = TryTimes(3) while t(): print "Your code to try several times"
Mam nadzieję, że można to poprawić, obsługując wyjątki w bardziej intuicyjny sposób. Otwarci na sugestie.
źródło
Możesz użyć
for
pętli zelse
klauzulą dla maksymalnego efektu: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
Kluczem jest wyjście z pętli, gdy tylko zapytanie się powiedzie.
else
Klauzula zostanie uruchomiona tylko wtedy, gdy pętla kończy bezbreak
.źródło