在Python中尝试直到没有错误


76

我在Python中有一段代码似乎可能导致错误,因为它正在访问服务器,有时该服务器有500个内部服务器错误。我想一直尝试直到没有收到错误为止。我的解决方案是:

while True:
    try:
        #code with possible error
    except:
         continue
    else:
         #the rest of the code
         break

对我来说,这似乎是一种hack。有没有更Python化的方式来做到这一点?


5
错误...当远程服务器死机时会发生什么?这会占用100%的CPU内核吗?
user9876 2011年

继续应该在其他,并在除外。是错字吗?
Ankit Jaiswal,

3
@aand:否。如果发生异常,他想再试一次(阅读:)continue,但是如果没有异常发生,他想对某些事情(用注释略述)并避免怪异地滥用循环。(else如果没有异常发生则执行,是丢失的片段吗?)

1
警告:您应该增加一定程度的保护,以防止持续失败,例如睡眠或最大尝试限制。否则,如@ user9876所述,您的进程将无限期消耗CPU。
布拉德·科赫

1
值得一提的是,相同要求的例子很少出现。我正在尝试使用新名称创建目录,因此我将一个数字附加到基数上并递增该数字,直到成功创建该目录为止。我只会遇到AlreadyExists错误。除非我的文件夹中包含无限目录,否则这显然迟早会成功。
笨拙的猫

Answers:


81

它不会变得更干净。这不是一件很干净的事情。充其量(无论如何,它都会更具可读性,因为的条件就在break那里while),您可以创建一个变量result = None并在其上循环is None。您还应该调整变量,并且可以continue用语义上正确的替换pass(您不在乎是否发生错误,您只想忽略它),然后删除break-这也会获得其余代码,该代码仅执行一次, 跳出循环。还要注意,由于文档中给出except:的原因,裸露的子句是邪恶

包含以上所有内容的示例:

result = None
while result is None:
    try:
        # connect
        result = get_data(...)
    except:
         pass
# other code that uses result but is not involved in getting it

7
如果持续存在连接失败的原因,则此解决方案将陷入无限循环。
布拉德·科赫

4
@BradKoch当然。这是问题所固有的,此外,任何解决方案(例如总体超时或有限的尝试次数)都与我描述的更改相对正交。

4
但是任何建议的答案都应该是安全的,或者至少要注意陷阱。这不能提供针对100%CPU消耗的保护,并危及将来的读者。
布拉德·科赫

@BradKoch您建议停止100%cpu使用率,在结束循环之前尝试5-10次吗?
Manakin

1
@Datanovice是的,一个简单的延迟重试限制是一个很好的解决方案。其他许多答案都说明了如何执行此操作。对于重试HTTP请求等复杂情况,我个人建议使用库。
布拉德·科赫

33

这是在4次尝试后硬失败的尝试,两次尝试之间等待2秒。根据您的需求进行更改,以从此表单中获取所需的内容:

from time import sleep

for x in range(0, 4):  # try 4 times
    try:
        # msg.send()
        # put your logic here
        str_error = None
    except Exception as str_error:
        pass

    if str_error:
        sleep(2)  # wait for 2 seconds before trying to fetch the data again
    else:
        break

这是带有退避的示例:

from time import sleep

sleep_time = 2
num_retries = 4
for x in range(0, num_retries):  
    try:
        # put your logic here
        str_error = None
    except Exception as str_error:
        pass

    if str_error:
        sleep(sleep_time)  # wait before trying to fetch the data again
        sleep_time *= 2  # Implement your backoff algorithm here i.e. exponential backoff
    else:
        break

6
我比其他人更喜欢这个答案,因为由于睡眠功能,它比其他过程“好”,而且尝试次数有限。
Ganesh Krishnan

25

也许是这样的:

connected = False

while not connected:
    try:
        try_connect()
        connected = True
    except ...:
        pass

5
任何建议的答案都应该是安全的,或者至少要注意陷阱。这不能提供针对100%CPU消耗的保护,并危及将来的读者。
布莱德·科赫

5

itertools.iter_except食谱封装这个想法“重复调用的函数,直到产生一个异常”的。它类似于已接受的答案,但是配方提供了一个迭代器。

从食谱:

def iter_except(func, exception, first=None):
    """ Call a function repeatedly until an exception is raised."""
    try:
        if first is not None:
            yield first()            # For database APIs needing an initial cast to db.first()
        while True:
            yield func()
    except exception:
        pass

您当然可以直接实现后者的代码。为了方便起见,我使用了一个单独的库,more_itertools该库为我们实现了此食谱(可选)。

import more_itertools as mit

list(mit.iter_except([0, 1, 2].pop, IndexError))
# [2, 1, 0]

细节

在这里,pop方法(或给定的函数)针对列表对象的每次迭代被调用,直到IndexError引发an为止。

对于您的情况,给定一些connect_function预期的错误,您可以使迭代器重复调用该函数,直到引发异常为止,例如

mit.iter_except(connect_function, ConnectionError)

在这一点上,通过循环遍历或调用,将其与其他任何迭代器一样对待next()


感谢您指出iter_except。如果它实施了一种退避方法,则该答案将更好,如下所示:stackoverflow.com/a/56517713/1231693
mellow-yellow

5

由于错误重试时,您应该始终:

  • 实施重试限制,否则您可能会陷入无限循环
  • 实施延迟,否则您将过于费力地浪费资源,例如您的CPU或已经苦恼的远程服务器

解决这些问题同时解决这些问题的简单通用方法是使用退避库。一个基本的例子:

import backoff

@backoff.on_exception(
    backoff.expo,
    MyException,
    max_tries=5
)
def make_request(self, data):
    # do the request

这段代码将make_request与实现重试逻辑的装饰器包装在一起。每当MyException发生特定错误时,我们都会重试,最多可重试5次。 在这种情况下,指数补偿是一个好主意,以帮助最大程度地减少我们重试在远程服务器上造成的额外负担。


3

这是我编写的实用程序函数,用于将重试包装直到成功后再包装到更整洁的程序包中。它使用相同的基本结构,但可以防止重复。可以对其进行修改以相对轻松地在最终尝试中捕获并重新抛出异常。

def try_until(func, max_tries, sleep_time):
    for _ in range(0,max_tries):
        try:
            return func()
        except:
            sleep(sleep_time)
    raise WellNamedException()
    #could be 'return sensibleDefaultValue'

然后可以这样称呼

result = try_until(my_function, 100, 1000)

如果您需要将参数传递给my_function,则可以通过try_until转发参数或将其包装在无参数lambda中来实现:

result = try_until(lambda : my_function(x,y,z), 100, 1000)

2

也许基于装饰器?您可以将要重试的异常列表和/或尝试次数作为装饰器参数传递。

def retry(exceptions=None, tries=None):
    if exceptions:
        exceptions = tuple(exceptions)
    def wrapper(fun):
        def retry_calls(*args, **kwargs):
            if tries:
                for _ in xrange(tries):
                    try:
                        fun(*args, **kwargs)
                    except exceptions:
                        pass
                    else:
                        break
            else:
                while True:
                    try:
                        fun(*args, **kwargs)
                    except exceptions:
                        pass
                    else:
                        break
        return retry_calls
    return wrapper


from random import randint

@retry([NameError, ValueError])
def foo():
    if randint(0, 1):
        raise NameError('FAIL!')
    print 'Success'

@retry([ValueError], 2)
def bar():
    if randint(0, 1):
        raise ValueError('FAIL!')
    print 'Success'

@retry([ValueError], 2)
def baz():
    while True:
        raise ValueError('FAIL!')

foo()
bar()
baz()

当然,“ try”部分应移至另一个函数,因为我们在两个循环中都使用了它,但这只是示例;)


稍作评论,但可以通过在itertools.repeat(None,times = tries)中使用_来避免代码加倍:如果try为None,则循环将永远继续,但是如果try为数字,经过多次迭代后终止。
Eldritch Cheese 2014年

2

像大多数其他应用程序一样,我建议尝试有限次数并在两次尝试之间休眠。这样,就不会发现自己陷入了无限循环,以防远程服务器实际发生任何事情。

我还建议仅在您遇到特定异常时才继续。这样,您仍然可以处理您可能不会想到的异常。

from urllib.error import HTTPError
import traceback
from time import sleep


attempts = 10
while attempts > 0:
    try:
        #code with possible error
    except HTTPError:
        attempts -= 1
        sleep(1)
        continue
    except:
        print(traceback.format_exc())

    #the rest of the code
    break

另外,您不需要else块。由于except块中的continue,因此可以跳过循环的其余部分,直到try块起作用,while条件得到满足或出现HTTPError以外的异常。


2

pypi上重试库如何?我已经使用了一段时间,它确实可以满足我的要求(更多信息,重试错误,无时重试,超时重试)。以下是他们网站上的示例:

import random
from retrying import retry

@retry
def do_something_unreliable():
    if random.randint(0, 10) > 1:
        raise IOError("Broken sauce, everything is hosed!!!111one")
    else:
        return "Awesome sauce!"

print do_something_unreliable()

1
e = ''
while e == '':
    try:
        response = ur.urlopen('https://https://raw.githubusercontent.com/MrMe42/Joe-Bot-Home-Assistant/mac/Joe.py')
        e = ' '
    except:
        print('Connection refused. Retrying...')
        time.sleep(1)

这应该工作。它将e设置为“”,而while循环检查是否仍为“”。如果在try语句中发现错误,它将打印连接被拒绝,等待1秒钟,然后重新开始。它会一直持续到try没有错误为止,然后将e设置为'',这会杀死while循环。


0

这是我用来将错误捕获为字符串的一小段代码。将重试,直到成功。这样可以捕获所有异常,但是您可以根据需要更改它。

start = 0
str_error = "Not executed yet."
while str_error:
    try:
        # replace line below with your logic , i.e. time out, max attempts
        start = raw_input("enter a number, 0 for fail, last was {0}: ".format(start))
        new_val = 5/int(start)
        str_error=None
    except Exception as str_error:
         pass

警告:此代码将陷入永久循环,直到没有异常发生为止。这只是一个简单的示例,MIGHT可能要求您尽快退出循环或在重试之间进入睡眠状态。


0

我现在尝试这样做,这就是我想出的;

    placeholder = 1
    while placeholder is not None:
        try:
            #Code
            placeholder = None
        except Exception as e:
            print(str(datetime.time(datetime.now()))[:8] + str(e)) #To log the errors
            placeholder = e
            time.sleep(0.5)
            continue

0

这里有许多不同的答案,但我感到没有一个可以成功满足我的需求(或更重要的是,提问者的需求),因此我结合了一些出色的答案,并放入自己的不太秘密的调味料具有以下配方:

successful = False
attempts = [0]
authorized_attempts = 10
while sum(attempts) < authorized_attempts and not successful:
    try:
        attempts.append(1)
        if EXPECTED_RESULT in result():
            print(f"Success on attempt number {sum(attempts)}.")
            attempts = [0]
            successful = True
    except Exception:
         print(f"Failure on attempt number {sum(attempts)}.")
         time.sleep(5)
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.