405

I have a loop starting with for i in range(0, 100). Normally it runs correctly, but sometimes it fails due to network conditions. Currently I have it set so that on failure, it will continue in the except clause (continue on to the next number for i).

Is it possible for me to reassign the same number to i and run through the failed iteration of the loop again?

smci
  • 32,567
  • 20
  • 113
  • 146
FurtiveFelon
  • 14,714
  • 27
  • 76
  • 97
  • 2
    You can use `range(100)` without the first parameter. If you use Python 2.x you could even use `xrange(100)`, this generates an iterator and uses less memory. (Not that it matters with only 100 objects.) – Georg Schölly Jan 18 '10 at 06:50
  • 13
    This question may be helpful: [is there a pythonic way to try something up to a maximum number of times?](http://stackoverflow.com/questions/567622/is-there-a-pythonic-way-to-try-something-up-to-a-maximum-number-of-times) – Elias Zamaria Aug 18 '10 at 19:58
  • 2
    there's a very elegant solution using decorators with support for handling arbitary exeptions in that thread – zitroneneis May 28 '13 at 19:18

30 Answers30

550

Do a while True inside your for loop, put your try code inside, and break from that while loop only when your code succeeds.

for i in range(0,100):
    while True:
        try:
            # do stuff
        except SomeSpecificException:
            continue
        break
zneak
  • 134,922
  • 42
  • 253
  • 328
  • 1
    `continue` does not use the same element of the iterable; instead it continues to the next. – Ignacio Vazquez-Abrams Jan 18 '10 at 05:22
  • @zneak:"I was wondering whether it is possible for me to reassign the same number to i and run through that the failed iteration of loop again." – Ignacio Vazquez-Abrams Jan 18 '10 at 05:28
  • 42
    @Ignacio, **huh**? `continue` retries the `while` loop, of course, **not** the `for` (!), so `i` is **not** "the next" anything -- it's exactly the same as it was on a previous (failed) leg of the same `while`, of course. – Alex Martelli Jan 18 '10 at 06:05
  • 19
    As xorsyst notes, it's advisable to put a retry limit on there. Otherwise you could get stuck looping for quite some time. – Brad Koch Aug 29 '13 at 00:34
  • 5
    This is an excellent example: https://medium.com/@echohack/patterns-with-python-poll-an-api-832173a03e93 – Tony Melony Dec 05 '14 at 12:09
  • 10
    I would definitely leave out the while True: line, otherwise the break wil continue the outer loop to exhaustion. – Jan Sep 17 '18 at 11:50
  • 1
    I would suggest to remove the for-loop from the shown example or use it instead of the `while True`. In my opinion, it has no beneficial value. It might confuse readers (as demonstrated in the comments above). – dubbaluga Dec 13 '18 at 12:11
  • This isn't quite right. In successful cases, the for loop continues till exhaustion. We want to break out of for loop too for successful case. – Sankalp Jan 17 '20 at 21:06
  • 6
    @Sankalp, it seems to me that this answer is proper for the question text. – zneak Jan 17 '20 at 21:16
  • I think that the a pure for loop is sufficient, as also described here: http://xion.io/post/code/python-retry-idiom.html – Martin Hepp Jul 19 '21 at 15:07
  • 3
    Though the answer is correct for the question asked, most will be confused by the first `for` loop and then the infinite `while`. If retrying a failed attempt x number of times is what you are looking for, a single for `for else` loop is probably what you want. – Kim Miller Feb 15 '22 at 19:45
334

I prefer to limit the number of retries, so that if there's a problem with that specific item you will eventually continue onto the next one, thus:

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
  • 7,897
  • 5
  • 39
  • 58
  • 1
    What are the consequences of the second `else` as relates to flow control? Under which circumstances is the "we failed..." part of the code executed? –  Jan 28 '15 at 05:30
  • 7
    @g33kz0r the for-else construct in Python executes the else clause if the for loop doesn't break. So, in this case, that section executes if we try all 10 attempts and always get an exception. – xorsyst Jan 28 '15 at 11:25
  • 3
    Don't you need a break at the end of the try: part? With the additional break in try:, if the process completes successfully the loop will break, if it doesn't complete successfully it will go straight to the exception part. Does that make sense? If I don't put a break at the end of try: it just does the thing 100 times. – Tristan Aug 11 '15 at 13:59
  • Tristan: You *do* want it to do the thing 100 times - a few more times (retries) if some of them fail. The comment in the code should perhaps say something like `# do thing with each i`. The outer loop is what you're really trying to do (iterate an action) and the inner loop is for retrying any failures that happen among those 100 actions. – Dennis Williamson Aug 12 '15 at 23:17
  • 3
    @Tristan - the `else` clause of the `try` does this "if successful, then break" that you are looking for. – PaulMcG Nov 10 '16 at 15:00
  • 2
    I also prefer a for-loop for retrying. A wrinkle in this code is that, if you want to re-raise the exception when you give up trying, you need something like "if attempt=9: raise" inside the `except` clause, and remember to use 9 and not 10. – PaulMcG Nov 10 '16 at 15:02
  • Good point @PaulMcGuire. As an alternative you could have a local variable `last_exception` that you assign in the attempt loop, and then just raise that if it's not None in the outer else. – xorsyst Nov 14 '16 at 14:10
  • @PaulMcG Isn't that what the final `else` clause is for? If you put `# do thing` in that clause, an unhandled exception should be raised (unless the action suceeds). – Jonas Dahlbæk Jan 26 '18 at 07:32
  • @xorsyst how to actually reconnect with the `except` part? Say if you try to connect to a URL in the `try` part but failed due to some usual errors, in most cases these are transient errors and may be fixed easily with retry, you want to reconnect with the URL in the except part, provided that all the URLs in the outer loop are actually accessible. This may be very easy to fix but I'm really new to python and data scraping, would you kindly help me out of this? – Jia Gao Apr 03 '18 at 01:00
  • @JasonGoal that sounds like a more-specific question, and I'm not really sure what you're asking. I suggest raising a new stack-overflow question with some example code. – xorsyst Apr 04 '18 at 10:34
  • What's the best way, in the "deal with the consequences" clause, to re-raise the most recent exception for the caller to deal with? does it work to simply rebind the exception in the `except` clause, then `raise from rebound_exception` or something like that? – Dubslow May 13 '23 at 04:29
  • Or actually, the `try` docs say that a lot of context is lost outside the `except` clause, so maybe I should just do `if i == 0: raise e` in the `except` clause? – Dubslow May 13 '23 at 04:41
110

UPDATE 2021-12-01:

As of June 2016, the retrying package is no longer being maintained. Consider using the active fork github.com/jd/tenacity, or alternatively github.com/litl/backoff.


The retrying package is a nice way to retry a block of code on failure.

For example:

@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")
Miron Veryanskiy
  • 343
  • 7
  • 12
goneri
  • 1,545
  • 1
  • 9
  • 7
  • 5
    More generally, pypi has multiple packages for retry decorators: https://pypi.python.org/pypi?%3Aaction=search&term=retry+decorator&submit=search – kert Apr 16 '16 at 21:14
  • is there anyway you can print the number of the retry attempt every time it fails? – dim_user Nov 13 '18 at 23:29
  • 25
    As I understood is not maintained, more active fork is https://github.com/jd/tenacity and maybe https://github.com/litl/backoff can be used too. – Alexey Shrub Jan 24 '19 at 07:45
  • 2
    The retrying package is alive again since 2022-09-03 with a new maintainer. – selle Feb 14 '23 at 14:30
53

Here is a solution similar to others, but it will raise the exception if it doesn't succeed in the prescribed number or retries.

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
  • 6,954
  • 3
  • 27
  • 52
  • 3
    Nice answer, but the variable name `retries` is misleading. It should much rather be `tries`. – Lukas Aug 17 '16 at 16:48
  • 2
    Very good solution thank you. It could be improved by adding a delay between each try. Very useful when dealing with APIs. – Sam Dec 31 '17 at 14:59
29

Alternatives to retrying: tenacity and backoff (2020 update)

The retrying library was previously the way to go, but sadly it has some bugs and it hasn't got any updates since 2016. Other alternatives seem to be backoff and tenacity. During the time of writing this, the tenacity had more GItHub stars (2.3k vs 1.2k) and was updated more recently, hence I chose to use it. Here is an example:

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

for i in range(100):
    do_something_unreliable(i)

The above code outputs something like:

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

More settings for the tenacity.retry are listed on the tenacity GitHub page.

Niko Föhr
  • 28,336
  • 10
  • 93
  • 96
  • Great answer! I spent hours trying to figure out the decorator, partial stuff.... Could you make your retry_on_communication_error a function that can accept parameters? something like def retry_on_communication_error(para1, para2)? – Jerry T Jan 02 '21 at 23:56
  • I'm not entirely sure what you mean, but (1) If you want to pass more parameters to the function that is to be retried, just add more parameters to the function definition. In this example, there is `do_something_unreliable(i)`, but you could have `do_something_unreliable(i, j, k)` if you wish. (2) If you want to pass some other parameters for the `tenacity.retry`, just add them to the `partial`. – Niko Föhr Jan 03 '21 at 01:00
  • This answer was super helpful. @np8 what do I do differently if I want to use retry_on_communication_error in a few different files? – user613 Apr 28 '22 at 15:40
  • You can define it in one module, like above, and then import it as any other function – Niko Föhr Apr 28 '22 at 19:28
16

The more "functional" approach without using those ugly while loops:

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

tryAgain()
restbeckett
  • 241
  • 2
  • 2
  • 24
    I'm sorry, but it seems much uglier than the "ugly while loops" variants; and I am fond of functional programming... – lvella Sep 09 '11 at 17:39
  • 10
    You need to make sure you don't recurse deeply though - the default stack size in Python is 1000 – Cal Paterson Aug 21 '14 at 10:40
  • 9
    If this is going to be 'functional', the recursion should be: `except: tryAgain(retries+1)` – quamrana Aug 01 '17 at 13:18
  • The problem with this is that we need to pass error around as variables. – lowzhao Apr 28 '20 at 01:56
  • Also there is a 1000 limit for recursion – Marat Mkhitaryan Nov 04 '20 at 15:30
  • Who want to retry more than 1000 times? This way is better than the accepted answer because it adds retries to avoid dead loop, but can be improved by add an increasing delay time to avoid flood server. – Jim Dec 04 '20 at 06:45
10

The clearest way would be to explicitly set i. For example:

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

    except MyException:
        continue
James Gentes
  • 7,528
  • 7
  • 44
  • 64
Tomi Kyöstilä
  • 1,269
  • 9
  • 13
9
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

My version is similar to several of the above, but doesn't use a separate while loop, and re-raises the latest exception if all retries fail. Could explicitly set err = None at the top, but not strictly necessary as it should only execute the final else block if there was an error and therefore err is set.

n8henrie
  • 2,737
  • 3
  • 29
  • 45
  • I think this is the best looking pattern here out of all. However, for someone not coming from python it is really strange that "err" escapes the scope of the for block. – niid Mar 29 '21 at 21:20
  • 1
    Yeah, it might look better with an explicit `err = None` at the top in some ways, but as I noted I don't think it's actually ever used. As a matter of fact, one could then omit the `else` and use a golang-esque `if err` or `if err is not None` in case err could somehow have a falsely value. – n8henrie Mar 29 '21 at 21:25
7

A generic solution with a timeout:

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)

Usage:

for retry in onerror_retry(SomeSpecificException, do_stuff):
    retry()
Laurent LAPORTE
  • 21,958
  • 6
  • 58
  • 103
  • Is it possible to specify a separate function for error checking? It would take the output of the callback and pass to the error checking function to decide if it was a failure or success instead of using a simple `except exception:` – Pratik Khadloya Oct 10 '17 at 21:04
  • Instead of a `try … except` you can use a `if` statement. But it is less pythonic. – Laurent LAPORTE Oct 10 '17 at 21:07
  • This solution does not work. https://trinket.io/python/caeead4f6b The exception thrown by do_stuff does not bubble to the generator. Why would it, anyway? do_stuff is called in the body of the for loop, which is on an outer level on its own, not nested in the generator. – isarandi Apr 17 '18 at 08:56
  • Your right, but for a different reason: the ``callback`` function is never called. I have forgotten the parenthesis, replace by ``callback()``. – Laurent LAPORTE Apr 17 '18 at 12:50
5

Using while and a 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
Ranju R
  • 2,407
  • 21
  • 16
5

Using recursion

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
  • 484
  • 1
  • 5
  • 11
5

There is something similar in the Python Decorator Library.

Please bear in mind that it does not test for exceptions, but the return value. It retries until the decorated function returns True.

A slightly modified version should do the trick.

kindall
  • 178,883
  • 35
  • 278
  • 309
Michael
  • 2,826
  • 4
  • 25
  • 18
5

Decorator is a good approach.

from functools import wraps
import time

class retry:
    def __init__(self, success=lambda r:True, times=3, delay=1, raiseexception=True, echo=True):
        self.success = success
        self.times = times
        self.raiseexception = raiseexception
        self.echo = echo
        self.delay = delay
    def retry(fun, *args, success=lambda r:True, times=3, delay=1, raiseexception=True, echo=True, **kwargs):
        ex = Exception(f"{fun} failed.")
        r = None
        for i in range(times):
            if i > 0:
                time.sleep(delay*2**(i-1))
            try:
                r = fun(*args, **kwargs)
                s = success(r)
            except Exception as e:
                s = False
                ex = e
                # raise e
            if not s:
                continue
            return r
        else:
            if echo:
                print(f"{fun} failed.", "args:", args, kwargs, "\nresult: %s"%r)
            if raiseexception:
                raise ex
    def __call__(self, fun):
        @wraps(fun)
        def wraper(*args, retry=0, **kwargs):
            retry = retry if retry>0 else self.times
            return self.__class__.retry(fun, *args, 
                                        success=self.success, 
                                        times=retry,
                                        delay=self.delay,
                                        raiseexception = self.raiseexception,
                                        echo = self.echo,
                                        **kwargs)
        return wraper

some usage examples:

@retry(success=lambda x:x>3, times=4, delay=0.1)
def rf1(x=[]):
    x.append(1)
    print(x)
    return len(x)
> rf1()

[1]
[1, 1]
[1, 1, 1]
[1, 1, 1, 1]

4
@retry(success=lambda x:x>3, times=4, delay=0.1)
def rf2(l=[], v=1):
    l.append(v)
    print(l)
    assert len(l)>4
    return len(l)
> rf2(v=2, retry=10) #overwite times=4

[2]
[2, 2]
[2, 2, 2]
[2, 2, 2, 2]
[2, 2, 2, 2, 2]

5
> retry.retry(lambda a,b:a+b, 1, 2, times=2)

3
> retry.retry(lambda a,b:a+b, 1, "2", times=2)

TypeError: unsupported operand type(s) for +: 'int' and 'str'
guoyongzhi
  • 121
  • 1
  • 3
4

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

Here is a quick decorator to handle this. 7 lines, no dependencies.

def retry(exception=Exception, retries=3, delay=0):
    def wrap(func):
        for i in range(retries):
            try:
                return func()
            except exception as e:
                print(f'Retrying {func.__name__}: {i}/{retries}')
                time.sleep(delay)
        raise e
    return wrap

@retry()
def do_something():
  ...
@retry(HTTPError, retries=100, delay=3)
def download_something():
  ...

An addition that could be made is extending exception to handle multiple exceptions (splat a list).

fny
  • 31,255
  • 16
  • 96
  • 127
3

You can use Python retrying package. Retrying

It is written in Python to simplify the task of adding retry behavior to just about anything.

ManJan
  • 3,939
  • 3
  • 20
  • 22
3

I use following in my codes,

   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
H S Rathore
  • 1,954
  • 2
  • 15
  • 20
3

I use this, which can be used on any function:

def run_with_retry(func: callable, max_retries: int = 3, wait_seconds: int = 2, **func_params):
num_retries = 1
while True:
    try:
        return func(*func_params.values())
    except Exception as e:
        if num_retries > max_retries:
            print('we have reached maximum errors and raising the exception')
            raise e
        else:
            print(f'{num_retries}/{max_retries}')
            print("Retrying error:", e)
            num_retries += 1
            sleep(wait_seconds)

Call like this:

    def add(val1, val2):
        return val1 + val2

    run_with_retry(func=add, param1=10, param2=20)
Fjurg
  • 487
  • 3
  • 10
1

If you want a solution without nested loops and invoking break on success you could developer a quick wrap retriable for any iterable. Here's an example of a networking issue that I run into often - saved authentication expires. The use of it would read like this:

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
  • 8,692
  • 8
  • 56
  • 82
1

Here is my take on this issue. The following retry function supports the following features:

  • Returns the value of the invoked function when it succeeds
  • Raises the exception of the invoked function if attempts exhausted
  • Limit for the number of attempts (0 for unlimited)
  • Wait (linear or exponential) between attempts
  • Retry only if the exception is an instance of a specific exception type.
  • Optional logging of attempts
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

Usage:

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)

See my post for more info.

dux2
  • 1,770
  • 1
  • 21
  • 27
1

I like to use bool values for this, like so:

success = False
num_try = 0
while success is False:
    if num_try >= 10: # or any number
        # handle error how  you please
    try:
        # code
        success = True
    except Exception as e:
        # record or do something with exception if needed
        num_try += 1
Yugenswitch
  • 125
  • 2
  • 5
1

with this decorator, you can easily control errors

class catch:
    def __init__(self, max=1, callback=None):
        self.max = max 
        self.callback = callback 
    
    def set_max(self, max):
        self.max = max
    
    def handler(self, *args, **kwargs):
        self.index = 0
        while self.index < self.max: 
            self.index += 1
            try:
                self.func(self, *args, **kwargs)
        
            except Exception as error:
                if callable(self.callback):
                    self.callback(self, error, args, kwargs)
                
    def __call__(self, func):
        self.func = func
        return self.handler

import time
def callback(cls, error, args, kwargs):
    print('func args', args, 'func kwargs', kwargs)
    print('error', repr(error), 'trying', cls.index)
    if cls.index == 2:
        cls.set_max(4)
    
    else:
        time.sleep(1)
    
    
@catch(max=2, callback=callback)  
def test(cls, ok, **kwargs):
    raise ValueError('ok')

test(1, message='hello')
milad
  • 21
  • 2
1

If retrying a failed attempt x number of times is what you are looking for, a single for else loop is probably what you want. Consider this example with 3 attempts:

attempts = 3

for attempt in range(1, attempts+1):
    try:
        if attempt < 4:
            raise TypeError(f"Error raised on attempt: {attempt}")
        else:
            print(f'Attempt {attempt} finally worked.')
    except (TypeError) as error:
        print(f'Attempt {attempt} hit the exception.')
        continue
    else:
        break
else:
    print(f'Exit after final attempt: {attempt}')

print(f'\nGo on to execute other code ...')

Gives the output:

Attempt 1 hit the exception.
Attempt 2 hit the exception.
Attempt 3 hit the exception.
Exit after final attempt: 3

Go on to execute other code ...

And with one more attempt it succeeds:

attempts = 4

Gives the output:

Attempt 1 hit the exception.
Attempt 2 hit the exception.
Attempt 3 hit the exception.
Attempt 4 finally worked.

Go on to execute other code ...
Kim Miller
  • 886
  • 8
  • 11
1

You could have a dedicated function using return to short circuit the result. For example like this:

def my_function_with_retries(..., max_retries=100):
    for attempt in range(max_retries):
        try:
            return my_function(...)
        except SomeSpecificException as error:
            logging.warning(f"Retrying after failed execution: {error}")

    raise SomeOtherException()
Didi Bear
  • 356
  • 3
  • 13
1

I liked laurent-laporte's answer. Here's my version of it wrapped in a class with static methods and some examples. I implemented a retry count as another way to retry. Also added kwargs.

from typing import List
import time


class Retry:
    @staticmethod
    def onerror_retry(exception, callback, retries: int = 0, timeout: float = 0, timedelta: float = 0,
                      errors: List = None, **kwargs):
        """

        @param exception: The exception to trigger retry handling with.
        @param callback: The function that will potentially fail with an exception
        @param retries: Optional total number of retries, regardless of timing if this threshold is met, the call will
                        raise the exception.
        @param timeout: Optional total amount of time to do retries after which the call will raise an exception
        @param timedelta: Optional amount of time to sleep in between calls
        @param errors: A list to receive all the exceptions that were caught.
        @param kwargs: An optional key value parameters to pass to the function to retry.
        """
        for retry in Retry.__onerror_retry(exception, callback, retries, timeout, timedelta, errors, **kwargs):
            if retry: retry(**kwargs)  # retry will be None when all retries fail.

    @staticmethod
    def __onerror_retry(exception, callback, retries: int = 0, timeout: float = 0, timedelta: float = 0,
                        errors: List = None, **kwargs):
        end_time = time.time() + timeout
        continues = 0
        while True:
            try:
                yield callback(**kwargs)
                break
            except exception as ex:
                print(ex)
                if errors:
                    errors.append(ex)

                continues += 1
                if 0 < retries < continues:
                    print('ran out of retries')
                    raise

                if timeout > 0 and time.time() > end_time:
                    print('ran out of time')
                    raise
                elif timedelta > 0:
                    time.sleep(timedelta)


err = 0

#
# sample dumb fail function
def fail_many_times(**kwargs):
    global err
    err += 1
    max_errors = kwargs.pop('max_errors', '') or 1
    if err < max_errors:
        raise ValueError("I made boo boo.")
    print("Successfully did something.")

#
# Example calls
try:
    #
    # retries with a parameter that overrides retries... just because
    Retry.onerror_retry(ValueError, fail_many_times, retries=5, max_errors=3)
    err = 0
    #
    # retries that run out of time, with 1 second sleep between retries.
    Retry.onerror_retry(ValueError, fail_many_times, timeout=5, timedelta=1, max_errors=30)
except Exception as err:
    print(err)

0

I wrote this one. Hopefully it's clear to understand and easy to use.

from functools import wraps
from random import randrange
from time import sleep


def retry(retry_limit=3, delay=None, max_random_delay=60):
    """
    Retrying decorator with simple backoff logic.
    :param retry_limit: amount of retries before throwing exception
    :param delay: delay between attempts. If None, random delay each time
    :param max_random_delay: maximum random delay in seconds
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            retries = 0
            while retries < retry_limit:
                try:
                    print(f'trying to execute {func.__name__}: {retries + 1}/{retry_limit}')
                    return func(*args, **kwargs)

                except Exception as e:
                    retries += 1
                    if retries == retry_limit:
                        raise e

                    if delay is None:
                        sleep_time = randrange(max_random_delay)
                        print(f'sleep for {sleep_time} seconds')
                        sleep(sleep_time)
                    else:
                        print(f'sleep for {delay} seconds')
                        sleep(delay)
        return wrapper
    return decorator
Pantone877
  • 515
  • 6
  • 15
0

This is my answer, using a decorator approach, a decor function with parameters:

import time
def retry(retries=3, delay=0.5):
    def decor(func):
        def wrap(*args, **kwargs):
            for i in range(retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f'Retrying {func.__name__}: {i}/{retries}')
                    time.sleep(delay)
                    if(i == retries-1):
                        raise e
        return wrap
    return decor

@retry(retries = 5, delay=0.5)
def divide(a,b):
    print(a/b)

When you call the divide function like divide(6,0), it will retry 5 times, then throw the 'division by zero' exception.

divide(6,0)
Retrying divide: 0/5
Retrying divide: 1/5
Retrying divide: 2/5
Retrying divide: 3/5
Retrying divide: 4/5
Traceback (most recent call last):

  Cell In[48], line 1
    divide(6,0)
    ...
    ...
    print(a/b)

ZeroDivisionError: division by zero
-3

Here's my idea on how to fix this:

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)
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Amine
  • 11
  • 1
-3

i recently worked with my python on a solution to this problem and i am happy to share it with stackoverflow visitors please give feedback if it is needed.

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}')
micharaze
  • 957
  • 8
  • 25
-11

increment your loop variable only when the try clause succeeds

appusajeev
  • 2,129
  • 3
  • 22
  • 20