จะลองอีกครั้งหลังจากเกิดข้อยกเว้นได้อย่างไร


252

for i in range(0, 100)ฉันมีวงที่เริ่มต้นด้วย โดยปกติแล้วจะทำงานอย่างถูกต้อง แต่บางครั้งก็ล้มเหลวเนื่องจากสภาพเครือข่าย ขณะนี้ฉันได้ตั้งค่าไว้เพื่อไม่ให้ล้มเหลวจะcontinueอยู่ในข้อยกเว้น (ดำเนินการต่อไปยังหมายเลขถัดไปสำหรับi)

เป็นไปได้หรือไม่ที่ฉันจะกำหนดหมายเลขเดิมอีกครั้งให้iและเรียกใช้การวนซ้ำที่ล้มเหลวของลูปอีกครั้ง


1
คุณสามารถใช้range(100)โดยไม่มีพารามิเตอร์แรก หากคุณใช้ Python 2.x คุณสามารถใช้งานxrange(100)ได้สิ่งนี้จะสร้างตัววนซ้ำและใช้หน่วยความจำน้อยลง (ไม่ใช่ว่ามันมีวัตถุเพียง 100 ชิ้นเท่านั้น)
Georg Schölly


2
มีวิธีการแก้ปัญหาที่สง่างามมากโดยใช้นักตกแต่งที่มีการสนับสนุนสำหรับการจัดการ
ข้อยกเว้นทางโบราณคดี

คำตอบ:


380

ทำwhile Trueวงในสำหรับวงวนใส่tryโค้ดของคุณและแยกจากwhileวงวนนั้นก็ต่อเมื่อโค้ดของคุณสำเร็จ

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

31
@Ignacio ใช่มั้ย continueลองใหม่whileห่วงแน่นอนไม่forดังนั้น (!) iจะไม่ได้ "ต่อไป" อะไร - มันเหมือนกับมันเป็นก่อนหน้านี้ (ล้มเหลว) ขาของเดียวกันwhileแน่นอน
Alex Martelli

13
ในฐานะที่เป็น xorsyst บันทึกแนะนำให้ใส่ขีด จำกัด การลองใหม่ที่นั่น มิฉะนั้นคุณอาจติดลูปติดอยู่ได้ซักพัก
แบรดโคช์

2
นี่เป็นตัวอย่างที่ยอดเยี่ยม: medium.com/@echohack/ …
Tony Melony

7
แน่นอนว่าฉันจะออกไปในขณะที่ True: line มิฉะนั้นตัวแบ่งจะยังคงวนรอบนอกเพื่อการอ่อนเพลีย
ม.ค.

1
@Sankalp ดูเหมือนว่าคำตอบนี้เหมาะสำหรับข้อความคำถาม
zneak

189

ฉันชอบที่จะ จำกัด จำนวนครั้งในการลองใหม่อีกครั้งดังนั้นหากมีปัญหากับไอเท็มเฉพาะนั้น

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 โครงสร้าง for-else ใน Python เรียกใช้งานประโยคอื่นถ้า for for loop ไม่หยุด ดังนั้นในกรณีนี้ส่วนนั้นจะดำเนินการถ้าเราลองทั้ง 10 ครั้งและได้รับการยกเว้นเสมอ
xorsyst

7
นี่เป็นคำตอบที่ยอดเยี่ยม! สมควรได้รับการโหวตมากขึ้นจริงๆ มันสมบูรณ์แบบใช้สิ่งอำนวยความสะดวกในหลามโดยเฉพาะอย่างยิ่งเป็นที่รู้จักน้อยประโยคelse: for
pepoluan

2
คุณไม่จำเป็นต้องหยุดพักในตอนท้ายของการลอง: ส่วนใช่ไหม ด้วยการแบ่งเพิ่มเติมในการลอง: ถ้ากระบวนการเสร็จสมบูรณ์แล้วลูปจะหยุดถ้ามันไม่เสร็จสมบูรณ์ก็จะไปที่ส่วนข้อยกเว้น มันสมเหตุสมผลไหม ถ้าฉันไม่หยุดพักเมื่อลองทำมันจะทำ 100 ครั้ง
อุโมงค์

1
@Tristan - elseข้อtryนี้ทำ "ถ้าประสบความสำเร็จแล้วทำลาย" ที่คุณกำลังมองหา
PaulMcG

1
ฉันชอบ for-loop สำหรับลองใหม่อีกครั้ง ริ้วรอยในรหัสนี้คือถ้าคุณต้องการที่จะยกข้อยกเว้นเมื่อคุณพยายามอีกครั้งคุณต้องการบางสิ่งบางอย่างเช่น "if พยายาม = 9: เพิ่ม" ในexceptประโยคและอย่าลืมใช้ 9 และไม่ใช่ 10
PaulMcG

69

แพคเกจลองใหม่เป็นวิธีที่ดีที่จะลองใหม่อีกครั้งบล็อกของรหัสในความล้มเหลว

ตัวอย่างเช่น:

@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
โดยทั่วไป pypi มีหลายแพคเกจสำหรับตกแต่งลองใหม่อีกครั้ง: pypi.python.org/...
เคิร์ท

มีอยู่แล้วคุณสามารถพิมพ์จำนวนครั้งที่พยายามลองใหม่ทุกครั้งที่ล้มเหลว
dim_user

8
ในขณะที่ฉันเข้าใจว่าไม่ได้รับการบำรุงรักษาส้อมที่ใช้งานมากขึ้นคือgithub.com/jd/tenacityและบางทีgithub.com/litl/backoffก็สามารถใช้งานได้เช่นกัน
Alexey Shrub

23

นี่เป็นวิธีการแก้ปัญหาที่คล้ายกับผู้อื่น แต่จะเพิ่มข้อยกเว้นหากไม่ประสบความสำเร็จในจำนวนที่กำหนดหรือลองใหม่

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

คำตอบที่ดี แต่ชื่อตัวแปรretriesทำให้เข้าใจผิด มันควรจะค่อนข้างtriesมาก
ลูคัส

True @Lukas แก้ไขแล้ว.
TheHerk

ทางออกที่ดีมากขอบคุณ มันสามารถปรับปรุงได้โดยการเพิ่มความล่าช้าระหว่างการลองแต่ละครั้ง มีประโยชน์มากเมื่อจัดการกับ API
Sam

14

วิธีการ "ใช้งาน" มากขึ้นโดยไม่ใช้สิ่งที่น่าเกลียดในขณะที่ลูป:

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

tryAgain()

13
ฉันขอโทษ แต่ดูเหมือนว่าน่าเกลียดกว่ารุ่น "น่าเกลียดในขณะที่ลูป"; และผมรักการเขียนโปรแกรมการทำงาน ...
lvella

9
คุณต้องตรวจสอบให้แน่ใจว่าคุณไม่ได้รับการชดเชยอย่างลึกซึ้ง - ขนาดสแต็คเริ่มต้นใน Python คือ 1,000
Cal Paterson

5
ถ้าสิ่งนี้จะเป็น 'การทำงาน' การสอบถามซ้ำควรเป็น:except: tryAgain(retries+1)
quamrana

ปัญหานี้คือเราต้องส่งข้อผิดพลาดเป็นตัวแปร
lowzhao

11

iวิธีที่ชัดเจนที่จะกำหนดอย่างชัดเจน ตัวอย่างเช่น:

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

    except MyException:
        continue

37
นั่นคือ C หรือ C ++ ใช่ไหม ฉันไม่สามารถบอกได้
Georg Schölly

5
@Gorgorg นั่นคือ Python ตามที่ระบุไว้ในคำถาม หรือว่าคุณกำลังประชดประชันด้วยเหตุผลบางอย่าง?
Jakob Borg

3
สิ่งนี้ไม่ได้ทำตามที่ OP ร้องขอ มันอาจจะถ้าคุณใส่หลังi += 1 # do stuff
fmalina

5
ไม่ได้เป็น pythonic ควรใช้rangeกับสิ่งประเภทนี้
Mystic

2
ฉันเห็นด้วยนี่ควรใช้ช่วงแน่นอน
2662833

5

โซลูชันทั่วไปที่มีการหมดเวลา:

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)

การใช้งาน:

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

เป็นไปได้หรือไม่ที่จะระบุฟังก์ชั่นแยกต่างหากสำหรับการตรวจสอบข้อผิดพลาด? มันจะเอาท์พุทของการติดต่อกลับและส่งผ่านไปยังฟังก์ชั่นการตรวจสอบข้อผิดพลาดเพื่อตัดสินใจว่ามันเป็นความล้มเหลวหรือความสำเร็จแทนที่จะใช้ง่ายexcept exception:
Pratik Khadloya

แทนที่จะเป็นtry … exceptคุณสามารถใช้ifคำสั่ง แต่มันเป็น pythonic น้อย
Laurent LAPORTE

วิธีนี้ไม่ทำงาน trinket.io/python/caeead4f6bข้อผิดพลาดที่ถูกโยนโดย do_stuff ไม่ทำให้เกิดฟองกับเครื่องกำเนิด ทำไมถึงเป็นเช่นนั้น? do_stuff ถูกเรียกในเนื้อความของ for loop ซึ่งอยู่ในระดับนอกของมันเองไม่ซ้อนในเครื่องกำเนิด
isarandi

สิทธิ์ของคุณ แต่ด้วยเหตุผลอื่น: callbackฟังก์ชั่นนี้ไม่เคยถูกเรียก 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

เวอร์ชันของฉันคล้ายกับหลายอย่างที่กล่าวมาข้างต้น แต่ไม่ได้ใช้การwhileวนซ้ำแยกต่างหากและยกข้อยกเว้นล่าสุดอีกครั้งหากการลองใหม่ทั้งหมดล้มเหลว สามารถตั้งค่าไว้err = Noneที่ด้านบนได้อย่างชัดเจนแต่ไม่จำเป็นอย่างยิ่งเนื่องจากควรดำเนินการelseบล็อกสุดท้ายหากมีข้อผิดพลาดและerrถูกตั้งค่า


4

มีบางอย่างที่คล้ายกันในการเป็นมัณฑนากรห้องสมุดหลาม

โปรดจำไว้ว่ามันไม่ได้ทดสอบข้อยกเว้น แต่เป็นค่าตอบแทน มันลองอีกครั้งจนกว่าฟังก์ชั่นการตกแต่งจะส่งคืนเป็น True

รุ่นที่แก้ไขเล็กน้อยควรทำเคล็ดลับ


ต่อไปนี้เป็นวิธีแก้ไขสำหรับข้อยกเว้น saltycrane.com/blog/2009/11/trying-out-retry-decorator-python
Antony Denyer

4

ใช้ในขณะที่และเคาน์เตอร์:

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

ใช้การเรียกซ้ำ

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
ออกจากสภาพ? หรือสิ่งนี้มีค่าไม่สิ้นสุด 100 *
ingyhere

3

คุณสามารถใช้แพ็คเกจการลองใหม่ของ Python ได้ ลองใหม่

มันถูกเขียนใน Python เพื่อลดความซับซ้อนของงานในการเพิ่มพฤติกรรมการลองส่งซ้ำเพื่ออะไรก็ตาม


2

ทางเลือกretrying: tenacityและbackoff(อัปเดต 2020)

ลองใหม่ห้องสมุดก่อนหน้านี้วิธีที่จะไป แต่เศร้ามันมีข้อบกพร่องบางอย่างและมันก็ไม่ได้มีการปรับปรุงใด ๆ ตั้งแต่ปี 2016 ทางเลือกอื่น ๆ ดูเหมือนจะย้อนกลับและดื้อรั้น ในช่วงเวลาที่เขียนนี้ความดื้อรั้นมีดาว GItHub มากขึ้น (2.3k เทียบกับ 1.2k) และได้รับการอัปเดตเมื่อเร็ว ๆ นี้ดังนั้นฉันเลือกที่จะใช้ นี่คือตัวอย่าง:

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

โค้ดด้านบนแสดงผลลัพธ์ดังนี้:

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

การตั้งค่าเพิ่มเติมสำหรับtenacity.retryจดทะเบียนในความดื้อรั้นหน้า GitHub


1

หากคุณต้องการโซลูชันที่ไม่มีลูปซ้อนกันและเรียกใช้breakในการประสบความสำเร็จคุณสามารถพัฒนาคำย่อretriableสำหรับการทำซ้ำใด ๆ นี่คือตัวอย่างของปัญหาเครือข่ายที่ฉันพบบ่อย - การรับรองความถูกต้องบันทึกหมดอายุ การใช้มันจะอ่านดังนี้:

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

ฉันใช้รหัสต่อไปนี้ใน

   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

นี่คือปัญหาของฉันในเรื่องนี้ retryฟังก์ชั่นต่อไปนี้รองรับคุณสมบัติดังต่อไปนี้:

  • ส่งคืนค่าของฟังก์ชันที่เรียกใช้เมื่อสำเร็จ
  • เพิ่มข้อยกเว้นของฟังก์ชันที่เรียกใช้หากพยายามหมด
  • จำกัด สำหรับจำนวนครั้ง (0 สำหรับไม่ จำกัด )
  • รอ (เชิงเส้นหรือยกกำลัง) ระหว่างความพยายาม
  • ลองอีกครั้งหากข้อยกเว้นเป็นอินสแตนซ์ของประเภทข้อยกเว้นเฉพาะ
  • การบันทึกที่ไม่จำเป็นของความพยายาม
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

การใช้งาน:

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)

ดูโพสต์ของฉันสำหรับข้อมูลเพิ่มเติม


-2

นี่คือความคิดของฉันเกี่ยวกับวิธีการแก้ไข:

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
นี่เป็นวิธีการปิดฐาน
คริสจอห์นสัน

-2

ฉันเพิ่งทำงานกับหลามของฉันในการแก้ปัญหานี้และฉันยินดีที่จะแบ่งปันกับผู้เยี่ยมชม stackoverflow โปรดให้ข้อเสนอแนะหากมีความจำเป็น

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.