是否有一种pythonic方式尝试最多次?

Ben*_*Ben 77 python exception-handling

我有一个python脚本,它在共享的linux主机上查询MySQL服务器.出于某种原因,对MySQL的查询通常会返回"服务器已经消失"错误:

_mysql_exceptions.OperationalError: (2006, 'MySQL server has gone away')
Run Code Online (Sandbox Code Playgroud)

如果您之后立即再次尝试查询,它通常会成功.所以,我想知道在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])
Run Code Online (Sandbox Code Playgroud)

显然,我可以通过在except子句中再次尝试来做到这一点,但这非常难看,我觉得必须有一个体面的方法来实现这一点.

Dan*_*ana 86

怎么样:

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])
Run Code Online (Sandbox Code Playgroud)

  • 或者`for range_number in range(3)` (18认同)
  • 嗯,我有点像我的,因为它明确表示只有在异常情况下才会增加尝试. (7认同)
  • -1:不喜欢休息.喜欢"虽然没有完成,但尝试<3:"更好. (5认同)
  • 我喜欢休息,但不喜欢休息.这更像是C-ish而不是pythonic.因为我在范围内是更好的imho. (5认同)
  • 是的,我想我比大多数人更加偏执于无限的"while"循环. (2认同)
  • 我喜欢[Kiv](http://stackoverflow.com/a/574924/3182836)使用`while <condition>而不是成功:` - 在try子句结尾处的`success = True` - as替代'break`作为对这个答案的一个小改进. (2认同)
  • 这是错误且可怕的。第三次尝试后不会抛出任何错误。 (2认同)

dwc*_*dwc 76

在Dana的回答基础上,您可能希望将其作为装饰者:

def retry(howmany):
    def tryIt(func):
        def f():
            attempts = 0
            while attempts < howmany:
                try:
                    return func()
                except:
                    attempts += 1
        return f
    return tryIt
Run Code Online (Sandbox Code Playgroud)

然后...

@retry(5)
def the_db_func():
    # [...]
Run Code Online (Sandbox Code Playgroud)

使用该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
Run Code Online (Sandbox Code Playgroud)

然后...

@retry(5, MySQLdb.Error, timeout=0.5)
def the_db_func():
    # [...]
Run Code Online (Sandbox Code Playgroud)

要安装decorator模块:

$ easy_install decorator
Run Code Online (Sandbox Code Playgroud)

  • 装饰者也应该接受一个异常类,所以你不必使用bare; 即@retry(5,MySQLdb.Error) (2认同)

Eli*_*les 12

更新:有一个更好维护的重试库fork,称为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"
Run Code Online (Sandbox Code Playgroud)

  • 重试库已被[tenacity库](https://github.com/jd/tenacity)取代. (2认同)

web*_*kie 7

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])
Run Code Online (Sandbox Code Playgroud)


cdl*_*ary 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
Run Code Online (Sandbox Code Playgroud)

callee函数分解似乎打破了功能,以便很容易看到业务逻辑而不会陷入重试代码中.

  • +1:当语言包含为您执行此操作的代码结构时,我讨厌标记变量.对于奖励积分,在for上放置一个其他来处理失败的所有尝试. (4认同)

Kiv*_*Kiv 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
Run Code Online (Sandbox Code Playgroud)