programing tip

파이썬에서 오류가 없을 때까지 시도하십시오

itbloger 2020. 12. 30. 07:43
반응형

파이썬에서 오류가 없을 때까지 시도하십시오


나는 파이썬에 서버에 액세스하고 있고 때로는 그 서버에 500 내부 서버 오류가 있기 때문에 확률 적으로 오류를 일으키는 것으로 보이는 코드가 있습니다. 오류가 발생하지 않을 때까지 계속 시도하고 싶습니다. 내 솔루션은 다음과 같습니다.

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

이것은 나에게 해킹처럼 보입니다. 이것을 수행하는 더 파이썬적인 방법이 있습니까?


그다지 깨끗하지 않을 것입니다. 이것은 매우 깨끗한 일이 아닙니다. 기껏해야 (에 대한 조건 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

아마도 다음과 같습니다.

connected = False

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

다음은 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

다음은 성공할 때까지 재 시도를 깔끔한 패키지로 래핑하기 위해 작성한 유틸리티 함수입니다. 동일한 기본 구조를 사용하지만 반복을 방지합니다. 비교적 쉽게 최종 시도에서 예외를 포착하고 다시 던지도록 수정할 수 있습니다.

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.

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

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.

귀하의 경우 일부 connect_function및 예상 오류가 주어지면 예외가 발생할 때까지 반복적으로 함수를 호출하는 반복기를 만들 수 있습니다.

mit.iter_except(connect_function, ConnectionError)

이 시점에서 반복하거나을 호출하여 다른 반복자로 처리합니다 next().


데코레이터 기반일까요? 재 시도 할 예외 목록 및 / 또는 시도 횟수를 데코레이터 인수로 전달할 수 있습니다.

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'부분은 두 루프 모두에서 사용하기 때문에 다른 기능으로 이동해야하지만 단지 예일뿐입니다.)


Like most of the others, I'd recommend trying a finite number of times and sleeping between attempts. This way, you don't find yourself in an infinite loop in case something were to actually happen to the remote server.

I'd also recommend continuing only when you get the specific exception you're expecting. This way, you can still handle exceptions you might not expect.

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

Also, you don't need an else block. Because of the continue in the except block, you skip the rest of the loop until the try block works, the while condition gets satisfied, or an exception other than HTTPError comes up.


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)

This should work. It sets e to '' and the while loop checks to see if it is still ''. If there is an error caught be the try statement, it prints that the connection was refused, waits 1 second and then starts over. It will keep going until there is no error in try, which then sets e to ' ', which kills the while loop.


Here is a short piece of code I use to capture the error as a string. Will retry till it succeeds. This catches all exceptions but you can change this as you wish.

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

WARNING: This code will be stuck in a forever loop until no exception occurs. This is just a simple example and MIGHT require you to break out of the loop sooner or sleep between retries.


When retrying due to error, you should always:

  • implement a retry limit, or you may get blocked on an infinite loop
  • implement a delay, or you'll hammer resources too hard, such as your CPU or the already distressed remote server

A simple generic way to solve this problem while covering those concerns would be to use the backoff library. A basic example:

import backoff

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

This code wraps make_request with a decorator which implements the retry logic. We retry whenever our specific error MyException occurs, with a limit of 5 retries. Exponential backoff is a good idea in this context to help minimize the additional burden our retries place on the remote server.

ReferenceURL : https://stackoverflow.com/questions/4606919/in-python-try-until-no-error

반응형