Bagaimana cara mencoba kembali setelah pengecualian?


252

Saya memiliki loop dimulai dengan for i in range(0, 100). Biasanya berjalan dengan benar, tetapi kadang-kadang gagal karena kondisi jaringan. Saat ini saya telah mengaturnya sehingga pada kegagalan, itu akan continuedi dalam klausa kecuali (melanjutkan ke nomor berikutnya untuk i).

Apakah mungkin bagi saya untuk menetapkan kembali nomor yang sama idan menjalankan iterasi loop yang gagal lagi?


1
Anda dapat menggunakan range(100)tanpa parameter pertama. Jika Anda menggunakan Python 2.x yang bahkan bisa Anda gunakan xrange(100), ini menghasilkan iterator dan menggunakan lebih sedikit memori. (Bukan berarti itu penting hanya dengan 100 objek.)
Georg Schölly


2
ada solusi yang sangat elegan menggunakan dekorator dengan dukungan untuk menangani pengecualian arbiter di utas itu
zitroneneis

Jawaban:


380

Lakukan while Truedi dalam untuk loop Anda, masukkan trykode Anda di dalam, dan istirahat dari whileloop itu hanya ketika kode Anda berhasil.

for i in range(0,100):
    while True:
        try:
            # do stuff
        except SomeSpecificException:
            continue
        break

30
@ Ignacio, ya ? continueretries yang whilelingkaran, tentu saja, tidak yang for, jadi (!) iyang tidak "berikutnya" apa pun - itu persis sama seperti itu pada (gagal) leg sebelumnya sama while, tentu saja.
Alex Martelli

13
Sebagai catatan xorsyst, disarankan untuk menempatkan batas coba lagi di sana. Kalau tidak, Anda bisa terjebak perulangan untuk beberapa waktu.
Brad Koch

2
Ini adalah contoh yang bagus: medium.com/@echohack/…
Tony

7
Saya pasti akan meninggalkan sementara True: line, jika tidak istirahat akan melanjutkan loop luar sampai kelelahan.
Jan

1
@Sankalp, menurut saya jawaban ini sesuai untuk teks pertanyaan.
zneak

189

Saya lebih suka membatasi jumlah percobaan ulang, sehingga jika ada masalah dengan item tertentu Anda akhirnya akan melanjutkan ke yang berikutnya, dengan demikian:

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.

3
@ g33kz0r konstruksi for-else di Python mengeksekusi klausa lain jika for for loop tidak putus. Jadi, dalam hal ini, bagian itu dijalankan jika kita mencoba semua 10 upaya dan selalu mendapatkan pengecualian.
xorsyst

7
Ini jawaban yang bagus! Benar-benar layak mendapat lebih banyak upvotes. Ini sempurna menggunakan semua fasilitas di Python, terutama else:klausa yang kurang dikenal for.
pepoluan

2
Tidakkah Anda perlu istirahat di akhir percobaan: bagian? Dengan tambahan break in try :, jika proses selesai dengan sukses, loop akan rusak, jika tidak berhasil, itu akan langsung ke bagian pengecualian. Apakah itu masuk akal? Jika saya tidak memberi istirahat pada akhir mencoba: itu hanya melakukan hal 100 kali.
Tristan

1
@ Christian - elseklausa trymelakukan ini "jika berhasil, maka istirahat" yang Anda cari.
PaulMcG

1
Saya juga lebih suka for-loop untuk mencoba kembali. Kerutan dalam kode ini adalah bahwa, jika Anda ingin menaikkan kembali pengecualian ketika Anda menyerah mencoba, Anda memerlukan sesuatu seperti "jika usaha = 9: naikkan" di dalam exceptklausa, dan ingatlah untuk menggunakan 9 dan bukan 10.
PaulMcG

69

The paket Mencoba lagi adalah cara yang bagus untuk mencoba kembali blok kode pada kegagalan.

Sebagai contoh:

@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")

4
Secara umum, pypi memiliki beberapa paket untuk coba dekorator: pypi.python.org/...
kert

adakah di sana Anda dapat mencetak jumlah percobaan ulang setiap kali gagal?
dim_user

8
Seperti yang saya pahami tidak dipelihara, garpu yang lebih aktif adalah github.com/jd/tenacity dan mungkin github.com/litl/backoff dapat digunakan juga.
Alexey Shrub

23

Berikut adalah solusi yang mirip dengan yang lain, tetapi akan memunculkan pengecualian jika tidak berhasil dalam jumlah yang ditentukan atau coba lagi.

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

Jawaban yang bagus, tetapi nama variabelnya retriesmenyesatkan. Seharusnya lebih baik tries.
Lukas

@Lukas benar. Tetap.
TheHerk

Solusi yang sangat bagus terima kasih. Ini dapat ditingkatkan dengan menambahkan penundaan di antara setiap percobaan. Sangat berguna saat berurusan dengan API.
Sam

14

Pendekatan yang lebih "fungsional" tanpa menggunakan loop jelek tersebut:

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

tryAgain()

13
Maaf, tetapi tampaknya jauh lebih jelek daripada varian "jelek saat putaran"; dan saya suka pemrograman fungsional ...
lvella

9
Anda perlu memastikan bahwa Anda tidak berulang lagi secara mendalam - ukuran tumpukan default di Python adalah 1000
Cal Paterson

5
Jika ini akan menjadi 'fungsional', rekursi tersebut harus:except: tryAgain(retries+1)
quamrana

Masalah dengan ini adalah bahwa kita perlu menyampaikan kesalahan sebagai variabel.
lowzhao

11

Cara yang paling jelas adalah dengan mengatur secara eksplisit i. Sebagai contoh:

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

    except MyException:
        continue

37
Apakah itu C atau C ++? Saya tidak tahu.
Georg Schölly

5
@ Georg Itu Python, seperti yang dinyatakan dalam pertanyaan. Atau di mana Anda menjadi sarkastik karena suatu alasan?
Jakob Borg

2
Ini tidak melakukan apa yang diminta OP. Mungkin jika Anda menempatkan i += 1setelah # do stuff.
fmalina

5
Bukan pythonic. Harus digunakan rangeuntuk hal-hal semacam ini.
Mystic

2
Saya setuju, ini pasti harus menggunakan rentang.
user2662833

5

Solusi umum dengan batas waktu:

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)

Pemakaian:

for retry in onerror_retry(SomeSpecificException, do_stuff):
    retry()

Apakah mungkin untuk menentukan fungsi terpisah untuk pemeriksaan kesalahan? Ini akan mengambil output dari callback dan beralih ke fungsi pengecekan kesalahan untuk memutuskan apakah itu gagal atau sukses alih-alih menggunakan yang sederhanaexcept exception:
Pratik Khadloya

Alih-alih try … exceptAnda bisa menggunakan ifpernyataan. Tapi itu kurang pythonic.
Laurent LAPORTE

Solusi ini tidak berfungsi. trinket.io/python/caeead4f6b Pengecualian yang dilemparkan oleh do_stuff tidak menggelembung ke generator. Lagi pula, mengapa itu terjadi? do_stuff disebut di dalam tubuh for for loop, yang berada di level luar sendiri, tidak bersarang di generator.
isarandi

Hak Anda, tetapi karena alasan yang berbeda: callbackfungsinya tidak pernah dipanggil. Saya lupa tanda kurung, ganti dengan 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

Versi saya mirip dengan beberapa di atas, tetapi tidak menggunakan whileloop terpisah , dan memunculkan kembali pengecualian terbaru jika semua coba lagi gagal. Dapat secara eksplisit diatur err = Nonedi bagian atas, tetapi tidak sepenuhnya diperlukan karena seharusnya hanya menjalankan elseblok terakhir jika ada kesalahan dan karenanya errdiatur.



4

Menggunakan saat dan penghitung:

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

4

Menggunakan rekursi

for i in range(100):
    def do():
        try:
            ## Network related scripts
        except SpecificException as ex:
            do()
    do() ## invoke do() whenever required inside this loop

1
Kondisi keluar? Atau apakah ini menjalankan 100 * infinity?
sini

3

Anda bisa menggunakan paket percobaan Python. Coba lagi

Ini ditulis dengan Python untuk menyederhanakan tugas menambahkan perilaku coba lagi untuk apa saja.


2

Alternatif untuk retrying: tenacitydan backoff(pembaruan 2020)

The mencoba kembali perpustakaan sebelumnya cara untuk pergi, tapi sayangnya memiliki beberapa bug dan belum mendapatkan update sejak 2016. alternatif lain tampaknya backoff dan keuletan . Selama penulisan ini, keuletan memiliki lebih banyak bintang GItHub (2.3k vs 1.2k) dan diperbarui baru-baru ini, oleh karena itu saya memilih untuk menggunakannya. Berikut ini sebuah contoh:

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()

Kode di atas menghasilkan sesuatu seperti:

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.
.
.
.

Lebih banyak pengaturan untuk tenacity.retryterdaftar di halaman GitHub tenacity .


1

Jika Anda menginginkan solusi tanpa loop bersarang dan menjalankan breakkesuksesan, Anda dapat mengembangkan bungkus cepat retriableuntuk setiap iterable. Berikut adalah contoh masalah jaringan yang sering saya temui - otentikasi yang disimpan kedaluwarsa. Penggunaannya akan berbunyi seperti ini:

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

1

Saya menggunakan mengikuti kode saya,

   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

0

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


0

Inilah pendapat saya tentang masalah ini. Fungsi berikut retrymendukung fitur-fitur berikut:

  • Mengembalikan nilai fungsi yang dipanggil ketika berhasil
  • Meningkatkan pengecualian dari fungsi yang dipanggil jika upaya habis
  • Batas untuk jumlah percobaan (0 untuk tidak terbatas)
  • Tunggu (linear atau eksponensial) di antara upaya
  • Coba lagi hanya jika pengecualian adalah turunan dari tipe pengecualian tertentu.
  • Upaya pencatatan opsional
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

Pemakaian:

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)

Lihat posting saya untuk info lebih lanjut.


-2

Inilah ide saya tentang cara memperbaikinya:

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)

7
Ini jauh dari basis.
Chris Johnson

-2

Saya baru-baru ini bekerja dengan python saya pada solusi untuk masalah ini dan saya senang untuk membagikannya dengan pengunjung stackoverflow tolong beri umpan balik jika diperlukan.

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}')

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.