有没有一种pythonic的方法可以尝试最大次数的尝试?[重复]


85

我有一个python脚本正在查询共享Linux主机上的MySQL服务器。出于某种原因,对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子句中进行另一次尝试来做到这一点,但这非常丑陋,而且我觉得必须有一种不错的方法来实现这一目标。


2
那是个很好的观点。我大概睡了几秒钟。我不知道服务器上的MySQL安装有什么问题,但似乎确实失败了一秒钟,然后又失败了。

3
@Yuval-答:这是一项常见的任务。我怀疑它甚至是内置在Erlang中的。
jfs

1
仅提及也许没有错,Mysql有一个wait_timeout变量来配置mysql删除不活动的连接。
安迪

Answers:


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比起大多数人,我对无限循环的蔓延更加偏执。
09年

5
-1:不喜欢休息。像“未完成但尝试<3:”一样更好。
S.Lott

5
我喜欢休息,但不是一会儿。这比pythonic更像C-ish。因为我在范围内更好的恕我直言。
哈森

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
Dana

这应该是“回归FUNC()在try块,而不只是‘FUNC()’。
罗伯特Rossney

呸! 感谢您的注意。
dwc

您是否真的尝试运行此程序?没用 问题是tryIt函数中的func()调用在装饰函数后立即执行,而不是在实际调用装饰后的函数时执行。您需要另一个嵌套函数。
史蒂夫·洛斯

12

更新:重试库有一个维护得更好的分支,称为tenacity,它支持更多功能,并且通常更灵活。


是的,有一个retrying库,它具有一个装饰器,该装饰器实现可以组合的几种重试逻辑:

一些例子:

@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
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:否则就破...恶臭。最好选择一个“未完成时,计数!= tryp_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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.