Python: How to Retry After Exception – 3 Effective Ways

Updated: January 2, 2024 By: Guest Contributor Post a comment

Solution 1: Using a Loop

The loop method involves using a simple loop such as while to repeatedly try a block of code until it executes without raising an exception.

Detailed steps:

  • Create a flag to indicate that the code should keep retrying.
  • Use a while loop that executes as long as the flag is true.
  • Attempt the operation that might fail and catch exceptions.
  • If the operation succeeds, change the flag to false to exit the loop.
  • Set a maximum number of retries to prevent an infinite loop.

Here’s a complete code example:

max_retries = 3
attempts = 0
success = False

while not success and attempts < max_retries:
    try:
        # Code block that may throw an exception
        print('Trying...')
        # Imagine some code here that might fail
        success = True
    except Exception as e:
        attempts += 1
        print(f'Attempt {attempts}: {e}')
        # Log error or take corrective measures

if success:
    print('Operation succeeded!')
else:
    print('All attempts failed.')

Advantages:

  • Simple and straightforward to implement.
  • Does not require external libraries.
  • Good for quick solutions without additional dependencies.

Drawbacks:

  • Can lead to an infinite loop if not handled with care.
  • Lacks flexibility for handling time between retries or specific exception types.

Solution 2: Using Recursion

Recursion involves creating a function that calls itself if an exception occurs, thereby retrying the operation.

Here’re the steps you can follow:

  • Define a function to perform the operation that has potential to fail.
  • Include an exception handling block within that function.
  • If an exception is caught, the function calls itself.
  • Include a counter to prevent infinite recursion.

And here’s a practical example:

def retry_operation(retries_remaining=3):
    try:
        # Code block that may throw an exception
        print('Trying...')
        # Demo code success
        return 'Operation succeeded!'
    except Exception as e:
        print(f'Caught an exception: {e}')
        if retries_remaining > 0:
            return retry_operation(retries_remaining - 1)
        else:
            return 'All attempts failed.'

result = retry_operation()
print(result)

Pros and Cons of the Solution:

Pros:

  • Maintains the state of retries without an external variable or loop.
  • Clear and concise code structure.

Cons:

  • Potentially risky because of the nature of recursion; can hit the maximum recursion limit.
  • Each retry consumes a new frame on the call stack, which isn’t memory efficient.

Solution 3: Using a Decorator with Exponential Backoff

This solution entails creating a decorator function that implements retries with an exponential backoff strategy to give increasingly longer pauses between retries, which can be useful for network operations or rate-limited APIs.

The steps:

  • Define a decorator function to implement retry logic.
  • Implement a counter and an increasing delay within the decorator.
  • Use the decorator on whichever function requires retry logic.

Example:

import time
from functools import wraps


def retry_with_backoff(retries=3, delay=1, backoff=2):
    def retry_decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            nonlocal retries, delay
            while retries > 0:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    retries -= 1
                    print(f'Retrying in {delay} seconds...')
                    time.sleep(delay)
                    delay *= backoff
            return 'All attempts failed.'

        return wrapper

    return retry_decorator


@retry_with_backoff()
def some_operation():
    print('Trying...')
    # Code block that might fail
    raise Exception('Simulated failure')


print(some_operation())

Pros:

  • Modular and reusable logic across different functions.
  • Exponential backoff is good for use with APIs and services that impose rate limits.

Cons:

  • More complex than the other solutions.
  • May increase the total wait time substantially if a large number of retries are used.

Conclusion

When designing a python application, it is common to encounter operations that can fail due to exceptions. Having a retry mechanism is essential to ensure the robustness of your code, especially when dealing with network operations, I/O, APIs, or any operation that can fail because of transient errors. The solutions presented here range from a simple loop to recursive calls, and a more advanced decorator with exponential backoff. Each method has its pros and cons, and the choice of solution will depend on the specifics of the operation and how the application should handle failures. Understanding these patterns can greatly impact the reliability of your systems and give you the tools necessary to build resilient software.