có cách nào để thử thứ gì đó lên đến số lần tối đa không? [bản sao]


85

Tôi có một tập lệnh python đang truy vấn máy chủ MySQL trên máy chủ linux được chia sẻ. Vì một số lý do, các truy vấn đến MySQL thường trả về lỗi "máy chủ đã biến mất":

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

Nếu bạn thử lại truy vấn ngay sau đó, nó thường thành công. Vì vậy, tôi muốn biết liệu có cách hợp lý nào trong python để thử thực hiện một truy vấn và nếu nó không thành công, hãy thử lại, lên đến một số lần cố định. Có lẽ tôi muốn nó thử 5 lần trước khi từ bỏ hoàn toàn.

Đây là loại mã tôi có:

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])

Rõ ràng tôi có thể làm điều đó bằng cách thử một lần nữa trong điều khoản ngoại trừ, nhưng điều đó vô cùng xấu xí, và tôi có cảm giác phải có một cách hợp lý để đạt được điều này.


2
Đó là một điểm hay. Tôi có lẽ sẽ ngủ một giấc trong vài giây. Tôi không biết có vấn đề gì với quá trình cài đặt MySQL trên máy chủ, nhưng có vẻ như nó không thành công trong một giây và lần tiếp theo nó hoạt động.
Ben

3
@Yuval A: Đó là một nhiệm vụ chung. Tôi nghi ngờ rằng nó thậm chí còn được xây dựng trong Erlang.
jfs 19/02/09

1
Chỉ cần đề cập rằng có thể không có gì sai, Mysql có một biến wait_timeout để cấu hình mysql loại bỏ các kết nối không hoạt động.
andy

Câu trả lời:


97

Làm thế nào về:

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
Hoặcfor attempt_number in range(3)
cdleary 19/02/09

8
Chà, tôi thích cái của tôi vì nó nói rõ rằng những nỗ lực chỉ tăng lên trong trường hợp có ngoại lệ.
Dana

2
Vâng, tôi đoán tôi hoang tưởng về những whilevòng lặp vô hạn len lỏi vào hơn hầu hết mọi người.
cdleary

5
-1: Không thích nghỉ. Giống như "khi chưa hoàn thành và cố gắng <3:" tốt hơn.
S.Lott

5
Tôi thích thời gian nghỉ ngơi, nhưng không phải trong lúc này. Điều này giống C-ish hơn là pythonic. đối với tôi trong phạm vi là imho tốt hơn.
hasen

78

Dựa trên câu trả lời của Dana, bạn có thể muốn làm điều này với tư cách là người trang trí:

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

Sau đó...

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

Phiên bản nâng cao sử dụng decoratormô-đun

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

Sau đó...

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

Để cài đặt các decoratormô-đun :

$ easy_install decorator

2
Người trang trí có lẽ cũng nên sử dụng một lớp ngoại lệ, vì vậy bạn không cần phải sử dụng một ngoại trừ trần; tức là @retry (5, MySQLdb.Error)
cdleary

Xấu! Tôi chưa bao giờ nghĩ đến trang trí sử dụng: P
Dana

Đó phải là "trả về func () trong khối thử, không chỉ là" func () ".
Robert Rossney 20/02/09

Bah! Cảm ơn cho những người đứng đầu lên.
dwc 20/02/09

Bạn đã thực sự thử chạy cái này? Nó không hoạt động. Vấn đề là lệnh func () trong hàm tryIt được thực thi ngay khi bạn trang trí hàm chứ không phải khi bạn thực sự gọi hàm được trang trí. Bạn cần một hàm lồng nhau khác.
Steve Losh 20/02/09

12

CẬP NHẬT: có một nhánh được duy trì tốt hơn của thư viện thử lại được gọi là độ bền , hỗ trợ nhiều tính năng hơn và nói chung linh hoạt hơn.


Có, có thư viện thử lại , có trình trang trí thực hiện một số loại logic thử lại mà bạn có thể kết hợp:

Vài ví dụ:

@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
Thư viện thử lại đã được thay thế bằng thư viện kiên trì .
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
Bạn có thể thêm một người khác ở dưới cùng:else: raise TooManyRetriesCustomException
Bob Stein

6

Tôi muốn cấu trúc lại nó như vậy:

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

Bao thanh toán calleechức năng dường như chia nhỏ chức năng để dễ dàng xem logic nghiệp vụ mà không bị sa lầy vào mã thử lại.


-1: khác và phá vỡ ... icky. Thích câu "trong khi không xong và đếm! = Try_count" rõ ràng hơn là ngắt.
S.Lott

1
Có thật không? Tôi nghĩ nó có ý nghĩa hơn theo cách này - nếu ngoại lệ không xảy ra, hãy thoát ra khỏi vòng lặp. Tôi có thể quá sợ hãi về vòng lặp while vô hạn.
cdleary

4
+1: Tôi ghét các biến cờ khi ngôn ngữ bao gồm các cấu trúc mã để làm điều đó cho bạn. Đối với điểm thưởng, hãy đặt một điểm khác vào for để đối phó với việc thất bại tất cả các lần thử.
xorsyst

6

Giống như S.Lott, tôi thích một lá cờ để kiểm tra xem chúng ta đã hoàn thành chưa:

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. định nghĩa:

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

2. sử dụng:

try_three_times(lambda: do_some_function_or_express())

Tôi sử dụng nó để phân tích cú pháp ngữ cảnh html.


0

Đây là giải pháp chung của tôi:

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

Điều này cho phép mã như sau:

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

Cũng có thể:

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

Điều này có thể được cải thiện bằng cách xử lý các ngoại lệ theo cách trực quan hơn, tôi hy vọng. Mở cho các đề xuất.


0

Bạn có thể sử dụng một forvòng lặp với một elsemệnh đề để có hiệu lực tối đa:

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

Chìa khóa là thoát ra khỏi vòng lặp ngay khi truy vấn thành công. Các elsekhoản sẽ chỉ được kích hoạt nếu vòng lặp hoàn thành mà không có một break.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.