Como tentar novamente após a exceção?

252

Eu tenho um loop começando com for i in range(0, 100). Normalmente ele funciona corretamente, mas às vezes falha devido às condições da rede. Atualmente, eu o configurei para que, em caso de falha, esteja continuena cláusula de exceção (continue para o próximo número de i).

É possível reatribuir o mesmo número ie executar novamente a iteração com falha do loop?

FurtiveFelon
fonte
1
Você pode usar range(100)sem o primeiro parâmetro. Se você usa o Python 2.x, pode até usar xrange(100), isso gera um iterador e usa menos memória. (Não que isso importe com apenas 100 objetos.)
Georg Schölly
2
há uma solução muito elegante usando decoradores com suporte para manipulação exeptions arbitrários nesse segmento
zitroneneis

Respostas:

379

Faça um while Trueloop for inside, coloque seu trycódigo dentro e quebre esse whileloop somente quando o código for bem-sucedido.

for i in range(0,100):
    while True:
        try:
            # do stuff
        except SomeSpecificException:
            continue
        break
zneak
fonte
30
@Ignacio, hein ? continuerepete o whileloop, é claro, não o for(!), então nãoi é o "próximo" nada - é exatamente o mesmo que era em uma parte anterior (com falha) do mesmo , é claro. while
Alex Martelli
13
Como observa o xorsyst, é aconselhável colocar um limite de novas tentativas nele. Caso contrário, você poderá ficar preso em loop por algum tempo.
Brad Koch
2
Este é um excelente exemplo: medium.com/@echohack/…
Tony Melony
7
Definitivamente deixaria de fora a linha True:, caso contrário, o intervalo continuará o ciclo externo até a exaustão.
Jan
1
@Ankalp, parece-me que esta resposta é adequada para o texto da pergunta.
zneak 17/01
189

Prefiro limitar o número de novas tentativas, para que, se houver um problema com esse item específico, você continuará no próximo, assim:

for i in range(100):
  for attempt in range(10):
    try:
      # do thing
    except:
      # perhaps reconnect, etc.
    else:
      break
  else:
    # we failed all the attempts - deal with the consequences.
xorsyst
fonte
3
@ g33kz0r a construção for-else no Python executa a cláusula else se o loop for não quebrar. Portanto, nesse caso, essa seção será executada se tentarmos todas as 10 tentativas e sempre tivermos uma exceção.
xorsyst
7
Esta é uma ótima resposta! Realmente merece muito mais votos. Ele usa perfeitamente todas as facilidades do Python, especialmente a else:cláusula menos conhecida de for.
precisa saber é
2
Você não precisa de uma pausa no final da tentativa: parte? Com a interrupção adicional em try :, se o processo for concluído com êxito, o loop será interrompido; se não for concluído com êxito, será direcionado diretamente para a parte da exceção. Isso faz sentido? Se eu não parar no final da tentativa: ele faz a coisa 100 vezes.
Tristan
1
@Tristan - a elsecláusula do tryfaz isso "se for bem-sucedida e quebre" o que você está procurando.
PaulMcG
1
Também prefiro um loop for para tentar novamente. Uma desvantagem deste código é que, se você deseja aumentar novamente a exceção quando desistir de tentar, precisará de algo como "if try = 9: raise" dentro da exceptcláusula e lembre-se de usar 9 e não 10.
PaulMcG
69

O pacote de novas tentativas é uma ótima maneira de tentar novamente um bloco de código em caso de falha.

Por exemplo:

@retry(wait_random_min=1000, wait_random_max=2000)
def wait_random_1_to_2_s():
    print("Randomly wait 1 to 2 seconds between retries")
goneri
fonte
4
De modo mais geral, pypi tem vários pacotes para decoradores de repetição: pypi.python.org/...
kert
existe alguma maneira de imprimir o número da tentativa de repetição toda vez que ela falhar?
dim_user
8
Como eu entendi que não é mantido, o fork mais ativo é o github.com/jd/tenacity e talvez o github.com/litl/backoff também possa ser usado.
Alexey Shrub
23

Aqui está uma solução semelhante a outras, mas irá gerar a exceção se não for bem-sucedida no número ou nas tentativas prescritas.

tries = 3
for i in range(tries):
    try:
        do_the_thing()
    except KeyError as e:
        if i < tries - 1: # i is zero indexed
            continue
        else:
            raise
    break
TheHerk
fonte
Boa resposta, mas o nome da variável retriesé enganoso. Deveria muito ser tries.
Lukas
True @Lukas. Fixo.
TheHerk 17/08/16
Muito boa solução obrigado. Isso pode ser aprimorado adicionando um atraso entre cada tentativa. Muito útil ao lidar com APIs.
Sam
14

A abordagem mais "funcional" sem usar aqueles loops feios enquanto:

def tryAgain(retries=0):
    if retries > 10: return
    try:
        # Do stuff
    except:
        retries+=1
        tryAgain(retries)

tryAgain()
restbeckett
fonte
13
Sinto muito, mas parece muito mais feio do que as variantes "feias enquanto loops"; e eu gosto muito de programação funcional ...
lvella
9
Você precisa ter certeza de que você não recurse profundamente embora - o tamanho da pilha padrão no Python é 1000
Cal Paterson
5
Se isso vai ser 'funcional', a recursividade deve ser:except: tryAgain(retries+1)
quamrana
O problema disso é que precisamos passar o erro como variáveis.
lowzhao 28/04
11

A maneira mais clara seria definir explicitamente i. Por exemplo:

i = 0
while i < 100:
    i += 1
    try:
        # do stuff

    except MyException:
        continue
Tomi Kyöstilä
fonte
37
Isso é C ou C ++? Não sei dizer.
Georg Schölly 18/01/10
5
@ Georg Isso é Python, como indicado na pergunta. Ou onde você está sendo sarcástico por algum motivo?
Jakob Borg
2
Isso não faz o que o OP pediu. Pode se você colocar i += 1logo depois # do stuff.
Fmalina # 20/13
5
Não pitônico. Deve usar rangepara esse tipo de coisa.
Mystic
2
Eu concordo, isso definitivamente deve usar o alcance.
User2662833
5

Uma solução genérica com um tempo limite:

import time

def onerror_retry(exception, callback, timeout=2, timedelta=.1):
    end_time = time.time() + timeout
    while True:
        try:
            yield callback()
            break
        except exception:
            if time.time() > end_time:
                raise
            elif timedelta > 0:
                time.sleep(timedelta)

Uso:

for retry in onerror_retry(SomeSpecificException, do_stuff):
    retry()
Laurent LAPORTE
fonte
É possível especificar uma função separada para verificação de erros? Levaria a saída do retorno de chamada e passar para a função de verificação de erros para decidir se ele era um fracasso ou o sucesso em vez de usar um simplesexcept exception:
Pratik Khadloya
Em vez de um, try … exceptvocê pode usar uma ifdeclaração. Mas é menos pitônico.
Laurent LAPORTE 10/10
Esta solução não funciona. trinket.io/python/caeead4f6b A exceção lançada por do_stuff não borbulha no gerador. Por que, afinal? do_stuff é chamado no corpo do loop for, que está sozinho em um nível externo, não aninhado no gerador.
Isarandi 17/0418
Você está certo, mas por um motivo diferente: a callbackfunção nunca é chamada. Eu esqueci o parêntese, substitua por callback().
Laurent LAPORTE
5
for _ in range(5):
    try:
        # replace this with something that may fail
        raise ValueError("foo")

    # replace Exception with a more specific exception
    except Exception as e:
        err = e
        continue

    # no exception, continue remainder of code
    else:
        break

# did not break the for loop, therefore all attempts
# raised an exception
else:
    raise err

Minha versão é semelhante a várias das opções acima, mas não usa um whileloop separado e gera novamente a exceção mais recente se todas as novas tentativas falharem. Poderia ser definido explicitamente err = Nonena parte superior, mas não estritamente necessário, pois ele só deve executar o elsebloco final se houver um erro e, portanto, errestiver definido.

n8henrie
fonte
4

Usando while e um contador:

count = 1
while count <= 3:  # try 3 times
    try:
        # do_the_logic()
        break
    except SomeSpecificException as e:
        # If trying 3rd time and still error?? 
        # Just throw the error- we don't have anything to hide :)
        if count == 3:
            raise
        count += 1
Ranju R
fonte
4

Usando recursão

for i in range(100):
    def do():
        try:
            ## Network related scripts
        except SpecificException as ex:
            do()
    do() ## invoke do() whenever required inside this loop
Joseph Thomas
fonte
1
Condição de saída? Ou isso roda 100 * infinito?
ingyhere 17/05/19
3

Você pode usar o pacote de novas tentativas do Python. Repetindo

Está escrito em Python para simplificar a tarefa de adicionar comportamento de repetição a praticamente qualquer coisa.

ManJan
fonte
2

Alternativas para retrying: tenacitye backoff(atualização de 2020)

A tentar novamente biblioteca era anteriormente o caminho a percorrer, mas infelizmente tem alguns bugs e não tem nenhuma atualização desde 2016. Outras alternativas parecem ser de recuo e tenacidade . Durante o tempo em que escrevi isso, a tenacidade tinha mais estrelas no GItHub (2,3k vs 1,2k) e foi atualizada mais recentemente, portanto, escolhi usá-lo. Aqui está um exemplo:

from functools import partial
import random # producing random errors for this example

from tenacity import retry, stop_after_delay, wait_fixed, retry_if_exception_type

# Custom error type for this example
class CommunicationError(Exception):
    pass

# Define shorthand decorator for the used settings.
retry_on_communication_error = partial(
    retry,
    stop=stop_after_delay(10),  # max. 10 seconds wait.
    wait=wait_fixed(0.4),  # wait 400ms 
    retry=retry_if_exception_type(CommunicationError),
)()


@retry_on_communication_error
def do_something_unreliable(i):
    if random.randint(1, 5) == 3:
        print('Run#', i, 'Error occured. Retrying.')
        raise CommunicationError()

O código acima gera algo como:

Run# 3 Error occured. Retrying.
Run# 5 Error occured. Retrying.
Run# 6 Error occured. Retrying.
Run# 6 Error occured. Retrying.
Run# 10 Error occured. Retrying.
.
.
.

Mais configurações para o tenacity.retryestão listadas na página de tenacidade do GitHub .

np8
fonte
1

Se você deseja uma solução sem loops aninhados e invocando breakcom êxito, você pode desenvolver um empacotamento rápido retriablepara qualquer iterável. Aqui está um exemplo de um problema de rede em que me deparo com freqüência - a autenticação salva expira. O uso dele seria assim:

client = get_client()
smart_loop = retriable(list_of_values):

for value in smart_loop:
    try:
        client.do_something_with(value)
    except ClientAuthExpired:
        client = get_client()
        smart_loop.retry()
        continue
    except NetworkTimeout:
        smart_loop.retry()
        continue
Mikhail
fonte
1

Eu uso o seguinte nos meus códigos,

   for i in range(0, 10):
    try:
        #things I need to do
    except ValueError:
        print("Try #{} failed with ValueError: Sleeping for 2 secs before next try:".format(i))
        time.sleep(2)
        continue
    break
HS Rathore
fonte
0

attempts = 3
while attempts:
  try:
     ...
     ...
     <status ok>
     break
  except:
    attempts -=1
else: # executed only break was not  raised
   <status failed>

Voronin Roman
fonte
0

Aqui está minha opinião sobre esse assunto. A seguinte retryfunção suporta os seguintes recursos:

  • Retorna o valor da função chamada quando é bem-sucedida
  • Gera a exceção da função chamada se tentativas esgotadas
  • Limite para o número de tentativas (0 para ilimitado)
  • Aguarde (linear ou exponencial) entre tentativas
  • Tente novamente apenas se a exceção for uma instância de um tipo de exceção específico.
  • Registro opcional de tentativas
import time

def retry(func, ex_type=Exception, limit=0, wait_ms=100, wait_increase_ratio=2, logger=None):
    attempt = 1
    while True:
        try:
            return func()
        except Exception as ex:
            if not isinstance(ex, ex_type):
                raise ex
            if 0 < limit <= attempt:
                if logger:
                    logger.warning("no more attempts")
                raise ex

            if logger:
                logger.error("failed execution attempt #%d", attempt, exc_info=ex)

            attempt += 1
            if logger:
                logger.info("waiting %d ms before attempt #%d", wait_ms, attempt)
            time.sleep(wait_ms / 1000)
            wait_ms *= wait_increase_ratio

Uso:

def fail_randomly():
    y = random.randint(0, 10)
    if y < 10:
        y = 0
    return x / y


logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler(stream=sys.stdout))

logger.info("starting")
result = retry.retry(fail_randomly, ex_type=ZeroDivisionError, limit=20, logger=logger)
logger.info("result is: %s", result)

Veja meu post para mais informações.

dux2
fonte
-2

Aqui está minha idéia sobre como corrigir isso:

j = 19
def calc(y):
    global j
    try:
        j = j + 8 - y
        x = int(y/j)   # this will eventually raise DIV/0 when j=0
        print("i = ", str(y), " j = ", str(j), " x = ", str(x))
    except:
        j = j + 1   # when the exception happens, increment "j" and retry
        calc(y)
for i in range(50):
    calc(i)
Amina
fonte
7
Isso está muito fora da base.
22616 Chris Johnson
-2

Recentemente, trabalhei com o meu python em uma solução para esse problema e estou feliz em compartilhá-lo com os visitantes do stackoverflow. Envie um feedback, se necessário.

print("\nmonthly salary per day and year converter".title())
print('==' * 25)


def income_counter(day, salary, month):
    global result2, result, is_ready, result3
    result = salary / month
    result2 = result * day
    result3 = salary * 12
    is_ready = True
    return result, result2, result3, is_ready


i = 0
for i in range(5):
    try:
        month = int(input("\ntotal days of the current month: "))
        salary = int(input("total salary per month: "))
        day = int(input("Total Days to calculate> "))
        income_counter(day=day, salary=salary, month=month)
        if is_ready:
            print(f'Your Salary per one day is: {round(result)}')
            print(f'your income in {day} days will be: {round(result2)}')
            print(f'your total income in one year will be: {round(result3)}')
            break
        else:
            continue
    except ZeroDivisionError:
        is_ready = False
        i += 1
        print("a month does'nt have 0 days, please try again")
        print(f'total chances left: {5 - i}')
    except ValueError:
        is_ready = False
        i += 1
        print("Invalid value, please type a number")
        print(f'total chances left: {5 - i}')
Rashad Kabir
fonte
-9

incrementa sua variável de loop somente quando a cláusula try for bem-sucedida

appusajeev
fonte