Làm thế nào để thử lại sau ngoại lệ?


252

Tôi có một vòng lặp bắt đầu với for i in range(0, 100). Thông thường nó chạy chính xác, nhưng đôi khi nó bị lỗi do điều kiện mạng. Hiện tại tôi đã đặt nó để khi thất bại, nó sẽ continueở mệnh đề ngoại trừ (tiếp tục đến số tiếp theo cho i).

Tôi có thể gán lại cùng một số ivà chạy qua lần lặp thất bại của vòng lặp không?


1
Bạn có thể sử dụng range(100)mà không cần tham số đầu tiên. Nếu bạn sử dụng Python 2.x, bạn thậm chí có thể sử dụng xrange(100), điều này sẽ tạo ra một trình vòng lặp và sử dụng ít bộ nhớ hơn. (Không phải là vấn đề chỉ với 100 đối tượng.)
Georg Schölly


2
có một giải pháp rất thanh lịch bằng cách sử dụng các công cụ trang trí với sự hỗ trợ để xử lý các ngoại lệ trong khu vực đó
zitroneneis

Câu trả lời:


379

Thực hiện while Truebên trong vòng lặp for của bạn, đặt trymã của bạn vào bên trong và chỉ thoát khỏi whilevòng lặp đó khi mã của bạn thành công.

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

30
@Ignacio, hả ? Tất nhiên, continuethử lại whilevòng lặp, không phảifor(!), do đó, không phảii là "tiếp theo" bất cứ điều gì - tất nhiên giống hệt như trên một chân trước đó (thất bại) . while
Alex Martelli

13
Như ghi chú xorsyst, nên đặt giới hạn thử lại ở đó. Nếu không, bạn có thể bị mắc kẹt trong một thời gian khá lâu.
Brad Koch

2
Đây là một ví dụ tuyệt vời: Medium.com/@echohack/ Mạnh
Tony Melony

7
Tôi chắc chắn sẽ bỏ qua dòng True: nếu không, sự phá vỡ sẽ tiếp tục vòng lặp bên ngoài đến kiệt sức.
Ngày

1
@Sankalp, dường như với tôi rằng câu trả lời này phù hợp với văn bản câu hỏi.
zneak

189

Tôi thích giới hạn số lần thử lại, do đó, nếu có vấn đề với mục cụ thể đó, cuối cùng bạn sẽ tiếp tục vào lần tiếp theo, do đó:

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 cấu trúc for-other trong Python thực thi mệnh đề other nếu vòng lặp for không bị hỏng. Vì vậy, trong trường hợp này, phần đó sẽ thực thi nếu chúng ta thử tất cả 10 lần thử và luôn nhận được một ngoại lệ.
xorsyst

7
Đây là một câu trả lời tuyệt vời! Thực sự xứng đáng nhiều upvote hơn. Nó sử dụng hoàn hảo tất cả các tiện ích trong Python, đặc biệt là else:mệnh đề ít được biết đến của for.
pepoluan

2
Bạn không cần nghỉ ngơi khi kết thúc thử: một phần? Với lần ngắt bổ sung trong thử:, nếu quá trình hoàn thành thành công, vòng lặp sẽ bị phá vỡ, nếu nó không hoàn thành thành công, nó sẽ đi thẳng đến phần ngoại lệ. Điều đó có ý nghĩa? Nếu tôi không nghỉ ngơi khi kết thúc thử: nó chỉ thực hiện được 100 lần.
Tristan

1
@Tristan - elsemệnh đề của tryđiều này "nếu thành công, sau đó phá vỡ" mà bạn đang tìm kiếm.
PaulMcG

1
Tôi cũng thích một vòng lặp for để thử lại. Một nếp nhăn trong mã này là, nếu bạn muốn nêu lại ngoại lệ khi bạn từ bỏ việc thử, bạn cần một cái gì đó như " except
ifvor

69

Các gói thử lại là một cách tốt đẹp để thử lại một khối mã trên thất bại.

Ví dụ:

@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
Tổng quát hơn, pypi có nhiều gói cho trang trí retry: pypi.python.org/...
Kert

Có cách nào bạn có thể in số lần thử lại mỗi lần không?
dim_user

8
Theo tôi hiểu là không được duy trì, ngã ba hoạt động nhiều hơn là github.com/jd/tenacity và có thể github.com/litl/backoff cũng có thể được sử dụng.
Alexey Shrub

23

Đây là một giải pháp tương tự như các giải pháp khác, nhưng nó sẽ đưa ra ngoại lệ nếu nó không thành công trong số lượng quy định hoặc thử lại.

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

Câu trả lời hay, nhưng tên biến retrieslà sai. Nó nên được nhiều hơn là tries.
Lukas

Đúng @Lukas. Đã sửa.
TheHerk 17/8/2016

Giải pháp rất tốt cảm ơn bạn. Nó có thể được cải thiện bằng cách thêm độ trễ giữa mỗi lần thử. Rất hữu ích khi giao dịch với API.
Sam

14

Cách tiếp cận "chức năng" hơn mà không sử dụng các vòng lặp xấu xí đó:

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

tryAgain()

13
Tôi xin lỗi, nhưng nó có vẻ xấu hơn nhiều so với các biến thể "xấu trong khi vòng lặp"; và tôi thích lập trình chức năng ...
lvella

9
Bạn cần đảm bảo rằng bạn không tái diễn sâu mặc dù - kích thước ngăn xếp mặc định trong Python là 1000
Cal Paterson

5
Nếu điều này sẽ là 'chức năng', thì đệ quy nên là:except: tryAgain(retries+1)
quamrana

Vấn đề với điều này là chúng ta cần chuyển lỗi xung quanh dưới dạng các biến.
lowzhao

11

Cách rõ ràng nhất sẽ được thiết lập rõ ràng i. Ví dụ:

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

    except MyException:
        continue

37
Đó là C hay C ++? Tôi không thể nói.
Georg Schölly

5
@Georg Đó là Python, như đã nêu trong câu hỏi. Hoặc nơi bạn đang mỉa mai vì một số lý do?
Jakob Borg

2
Điều này không làm những gì OP yêu cầu. Nó có thể nếu bạn đặt i += 1ngay sau đó # do stuff.
fmalina

5
Không phải pythonic. Nên sử dụng rangecho loại công cụ này.
Huyền bí

2
Tôi đồng ý, điều này chắc chắn nên sử dụng phạm vi.
user2662833 17/05/2016

5

Một giải pháp chung với thời gian chờ:

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)

Sử dụng:

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

Có thể chỉ định một chức năng riêng để kiểm tra lỗi? Nó sẽ lấy đầu ra của hàm gọi lại và chuyển đến chức năng kiểm tra lỗi để quyết định xem đó là một thất bại hay thành công thay vì sử dụng một cách đơn giảnexcept exception:
Pratik Khadloya

Thay vì một try … exceptbạn có thể sử dụng một iftuyên bố. Nhưng nó là ít pythonic.
Laurent LAPORTE

Giải pháp này không hoạt động. trinket.io/python/caeead4f6b Ngoại lệ được ném bởi do_ ware không tạo bong bóng cho trình tạo. Tại sao nó, dù sao? do_ ware được gọi trong phần thân của vòng lặp for, ở cấp độ bên ngoài, không được lồng trong trình tạo.
isarandi

Quyền của bạn, nhưng vì một lý do khác: callbackchức năng không bao giờ được gọi. Tôi đã quên dấu ngoặc đơn, thay thế bằng 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

Phiên bản của tôi tương tự như một số phiên bản trên, nhưng không sử dụng một whilevòng lặp riêng biệt và sẽ đưa ra ngoại lệ mới nhất nếu tất cả các lần thử lại đều thất bại. Có thể đặt rõ ràng err = Noneở trên cùng, nhưng không thực sự cần thiết vì nó chỉ nên thực thi elsekhối cuối cùng nếu có lỗi và do đó errđược đặt.



4

Sử dụng while và counter:

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

Sử dụng đệ quy

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
Điều kiện thoát hiểm? Hay cái này chạy 100 * vô cùng?
ingyhere

3

Bạn có thể sử dụng gói thử lại Python. Đang thử lại

Nó được viết bằng Python để đơn giản hóa nhiệm vụ thêm hành vi thử lại vào bất cứ thứ gì.


2

Các lựa chọn thay thế cho retrying: tenacitybackoff(cập nhật 2020)

Các thử lại thư viện trước đây là con đường để đi, nhưng đáng tiếc nó có một số lỗi và nó đã không có bất kỳ bản cập nhật kể từ năm 2016. lựa chọn thay thế khác dường như là backoffkiên trì . Trong thời gian viết bài này, độ bền có nhiều sao GItHub hơn (2,3k so với 1,2k) và được cập nhật gần đây hơn, do đó tôi đã chọn sử dụng nó. Đây là một ví dụ:

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ạn mã trên xuất ra một cái gì đó như:

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

Thêm cài đặt cho tenacity.retryđược liệt kê trên trang GitHub độ bền .


1

Nếu bạn muốn một giải pháp không có các vòng lặp lồng nhau và gọi breakthành công, bạn có thể phát triển một gói nhanh retriablecho bất kỳ lần lặp nào. Đây là một ví dụ về sự cố mạng mà tôi thường gặp phải - xác thực đã lưu hết hạn. Việc sử dụng nó sẽ đọc như thế này:

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

Tôi sử dụng sau đây trong mã của tôi,

   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

Đây là nhận của tôi về vấn đề này. Các retrychức năng sau đây hỗ trợ các tính năng sau:

  • Trả về giá trị của hàm được gọi khi nó thành công
  • Tăng ngoại lệ của hàm được gọi nếu các nỗ lực hết
  • Giới hạn số lần thử (0 không giới hạn)
  • Chờ (tuyến tính hoặc hàm mũ) giữa các lần thử
  • Chỉ thử lại nếu ngoại lệ là một thể hiện của một loại ngoại lệ cụ thể.
  • Tùy chọn đăng nhập cố gắng
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

Sử dụng:

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)

Xem bài viết của tôi để biết thêm.


-2

Đây là ý tưởng của tôi về cách khắc phục điều này:

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
Đây là cách ra khỏi cơ sở.
Chris Johnson

-2

Gần đây tôi đã làm việc với con trăn của mình về một giải pháp cho vấn đề này và tôi rất vui khi chia sẻ nó với khách truy cập stackoverflow xin vui lòng cho phản hồi nếu cần thiết.

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

-9

chỉ tăng biến vòng lặp của bạn khi mệnh đề try thành công

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.