Python 有没有一种蟒蛇式的方法可以尝试最多次数的东西?

Python 有没有一种蟒蛇式的方法可以尝试最多次数的东西?,python,exception-handling,Python,Exception Handling,我有一个python脚本,它在共享linux主机上查询MySQL服务器。出于某些原因,对MySQL的查询通常会返回“服务器已离开”错误: 如果随后立即再次尝试查询,通常会成功。因此,我想知道python中是否有一种合理的方法来尝试执行查询,如果查询失败,则重试一次,最多重试一次。可能我希望在完全放弃之前尝试5次 以下是我的代码类型: conn = MySQLdb.connect(host, user, password, database) cursor = conn.cursor() try

我有一个python脚本,它在共享linux主机上查询MySQL服务器。出于某些原因,对MySQL的查询通常会返回“服务器已离开”错误:

如果随后立即再次尝试查询,通常会成功。因此,我想知道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])
conn=MySQLdb.connect(主机、用户、密码、数据库) 游标=连接游标() 尝试次数=0 当尝试<3时: 尝试: cursor.execute(查询) rows=cursor.fetchall() 对于行中的行: #对数据做点什么 打破 除了MySQLdb.Error,e: 尝试次数+=1 打印“MySQL错误%d:%s”%(e.args[0],e.args[1])
我会像这样重构它:

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

分解
被调用方
函数似乎会分解功能,以便在不陷入重试代码中的情况下轻松查看业务逻辑。

基于Dana的答案,您可能希望作为一名装饰者来完成以下工作:

def retry(howmany):
    def tryIt(func):
        def f():
            attempts = 0
            while attempts < howmany:
                try:
                    return func()
                except:
                    attempts += 1
        return f
    return tryIt
使用
decorator
模块的增强版 然后

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


像S.洛特一样,我喜欢用一面旗帜来检查我们是否完成了:

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
conn=MySQLdb.connect(主机、用户、密码、数据库) 游标=连接游标() 成功=错误 尝试次数=0 当尝试<3次且未成功时: 尝试: cursor.execute(查询) rows=cursor.fetchall() 对于行中的行: #对数据做点什么 成功=正确 除了MySQLdb.Error,e: 打印“MySQL错误%d:%s”%(e.args[0],e.args[1]) 尝试次数+=1
这是我的通用解决方案:

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
也可能:

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

我希望,通过以更直观的方式处理异常,可以改进这一点。欢迎建议。

更新:重试库中有一个维护更好的分支,名为,它支持更多功能,通常更灵活

def successful_transaction(transaction):
    try:
        transaction()
        return True
    except SQL...:
        return False

succeeded = any(successful_transaction(transaction)
                for transaction in repeat(transaction, 3))
API略有变化:

@retry(stop=stop_after_attempt(7))
def stop_after_7_attempts():
    print("Stopping after 7 attempts")

@retry(wait=wait_fixed(2))
def wait_2_s():
    print("Wait 2 second between retries")

@retry(wait=wait_exponential(multiplier=1, min=4, max=10))
def wait_exponential_1000():
    print("Wait 2^x * 1000 milliseconds between each retry,")
    print("up to 10 seconds, then 10 seconds afterwards")

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

一些例子:

@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")
1.定义:

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

我使用它来解析html上下文。

您可以使用
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
子句只有在循环完成而没有中断时才会被触发,这是一个很好的观点。我可能会睡上几秒钟。我不知道在服务器上安装MySQL有什么问题,但看起来它确实在一秒钟内失败了,下一秒钟就可以工作了。@Yuval A:这是一项常见的任务。我甚至怀疑它是Erlang中内置的。我想说的是,可能没有什么问题,Mysql有一个wait_timeout变量来配置Mysql删除非活动连接。或者
对于范围(3)内的尝试次数
好吧,我有点喜欢我的,因为它明确表示只有在发生异常时才会增加尝试次数。是的,我想我比大多数人更偏执于无限的
,而
循环却在悄悄溜进。比如“未完成和尝试<3:”更好。我喜欢休息,但不喜欢休息。这更像是C-ish而不是pythonic。因为范围内的i比imho更好。装饰程序可能也应该接受一个异常类,所以您不必使用裸的except;i、 e.@retry(5,MySQLdb.Error)漂亮!我从来没有想过使用decorators:p它应该是try块中的“return func(),而不仅仅是“func()”。呸!谢谢你的提醒。你真的试过运行它吗?它不起作用。问题是func()tryIt函数的调用会在您修饰函数时立即执行,而不是在您实际调用修饰函数时执行。您需要另一个嵌套函数。-1:else和break…讨厌。请选择更清晰的“while not done and count!”尝试计数“比休息好,真的吗?我认为这样做更有意义——如果异常没有发生,就中断循环。我可能过于害怕无限while循环。+1:我讨厌在语言中包含代码结构时使用标志变量。要获得额外积分,请在For上添加一个else来处理所有尝试失败的情况。您可以在底部添加一个:
else:raise TooManyRetriesCustomException
重试库已被替换。
def successful_transaction(transaction):
    try:
        transaction()
        return True
    except SQL...:
        return False

succeeded = any(successful_transaction(transaction)
                for transaction in repeat(transaction, 3))
@retry(stop=stop_after_attempt(7))
def stop_after_7_attempts():
    print("Stopping after 7 attempts")

@retry(wait=wait_fixed(2))
def wait_2_s():
    print("Wait 2 second between retries")

@retry(wait=wait_exponential(multiplier=1, min=4, max=10))
def wait_exponential_1000():
    print("Wait 2^x * 1000 milliseconds between each retry,")
    print("up to 10 seconds, then 10 seconds afterwards")
@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")
def try_three_times(express):
    att = 0
    while att < 3:
        try: return express()
        except: att += 1
    else: return u"FAILED"
try_three_times(lambda: do_some_function_or_express())
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