Designing Idempotent Functions
An AssertionError is raised when an assert statement's condition is False, and it appears in the traceback with the file name and line number where the assertion failed.
Why Retries Need Careful Tracing
A retry decorator can make a failed operation run again automatically. That convenience creates an important execution question: what exactly happens between the first failure and the next attempt? The wrapped function does not resume at the failed assertion. The decorator catches the exception, records the failed attempt, and re-executes the entire wrapped function. This repeated execution is the central behavior to understand when designing functions for retry-based systems.
A retry repeats the wrapped function as a whole. It does not restart only at the assertion that failed.
The Assertion Failure
An assert statement checks a condition. When that condition is False, Python raises an AssertionError immediately. The current function stops at that point, and the exception begins propagating up the call stack unless an exception handler catches it.
What do you think happens?
A decorated function reaches an assert statement whose condition is False. What happens next?
Reveal answer
Answer: Python raises an AssertionError and the decorator can handle it.
The false condition raises the exception immediately. Because the function is wrapped, the decorator is positioned to catch the exception before it reaches the caller, depending on the decorator's design.
Decorator Interception
A decorator sits between the caller and the wrapped function. The caller starts the decorated call, the decorator enters its execution process, and the decorator invokes the wrapped function. If the wrapped function raises an exception, control returns to the decorator's exception handler. The decorator can then log the failure, decide whether to retry, and either invoke the function again or allow the exception to propagate.
Retry Attempt Flow
A failed first attempt
Trace a decorated function whose first execution raises an AssertionError and whose retry executes the wrapped function again.
Start: The caller invokes the decorated function. Control first enters the decorator.
First execution: The decorator invokes the wrapped function. The function runs until its assert condition evaluates to False.
Raise: Python raises an AssertionError immediately and stops the current function.
Intercept: The decorator catches the exception, logs the failure with the attempt number, and checks whether another attempt is allowed.
Re-execute: If the retry policy allows another attempt, the decorator invokes the entire wrapped function again. It does not execute only the failed assertion.
Finish: The next execution either succeeds, fails again and reaches another retry decision, or eventually reaches the maximum number of attempts.
The retry mechanism controls the transition between attempts, while the wrapped function starts over from its beginning on each re-execution.
A retry loop is controlled by the decorator, but the work inside each attempt belongs to the wrapped function. Any side effect performed before the failure, such as a database write, happens again when the entire function is re-executed.
Reading the Traceback
A traceback identifies where an exception occurred by showing the file name, line number, and function associated with the failure. When decorators are involved, the traceback can contain decorator frames because those frames are part of the call stack. To locate the actual source of the error, inspect the deepest frame, which is listed last. That frame shows where the exception originated.
- Find the exception type, such as AssertionError.
- Read the traceback frames in order.
- Treat the deepest frame, listed last, as the location where the exception originated.
- Use that frame's file name and line number to locate the failed assertion in the source.
- Do not mistake a decorator frame for the source of the assertion failure.
Design Implications
The retry behavior gives a practical design requirement for functions that may be executed more than once: examine every operation that occurs before a possible failure. Because the decorator re-executes the complete wrapped function, side effects are not automatically limited to one execution. A database write performed before an assertion can occur again on a later attempt. Designing an idempotent function therefore requires careful attention to what repeated execution does to those operations.
| Execution detail | What happens on failure | What happens on retry |
|---|---|---|
| Assertion | A false condition raises AssertionError | The assertion is encountered again when the function starts over |
| Wrapped function | The current execution stops | The entire function is re-executed |
| Decorator | Catches and logs the exception | Decides whether another attempt is allowed |
| Database write | May already have occurred before the failure | Can occur again during the next full execution |
Assuming a retry resumes at the failed assertion
The decorator re-executes the entire wrapped function rather than only the line that failed.
Fix:
Trace the next attempt from the function's beginning and include every operation that runs before the assertion.Treating the decorator frame as the origin of the error
Decorator frames are part of the call stack, but the deepest frame shows where the exception actually originated.
Fix:
Read the last traceback frame and use its file name and line number to find the failed assertion.Assuming catching an exception means the operation succeeded
Catching lets the decorator decide whether to retry, propagate the last exception, or return a default value.
Fix:
Follow the decorator's next decision after logging the failed attempt.Ignoring repeated side effects
The entire function runs again, so the database write can happen again as well.
Fix:
List the operations performed before the failure and account for each one on every attempt.
Trace It Yourself
A decorated function performs a database write, reaches an assert statement whose condition is False, and raises an AssertionError. The decorator logs the failed attempt and allows another attempt. Trace the order of events from the caller's invocation through the second execution. Identify which operation can happen twice and identify which traceback frame you would inspect first to locate the assertion.
Hints
- Begin with the decorator, not directly with the wrapped function.
- The first attempt stops when the assertion condition is False.
- The next attempt re-executes the whole wrapped function.
- The deepest traceback frame identifies the originating source location.
Practice trace answer
Determine the control flow for a database write followed by a failed assertion inside a retry-decorated function.
First call: The caller enters the decorator, which invokes the wrapped function.
First write: The wrapped function performs the database write before reaching the assertion.
Failure: The false assertion condition raises AssertionError and stops that execution of the function.
Retry decision: The decorator catches and logs the exception, then determines that another attempt is allowed.
Second write: The decorator re-executes the entire wrapped function, so the database-write step is reached again.
Traceback lookup: The deepest traceback frame points to the file name and line number where the assertion originated.
The database-write operation can occur on both executions, while the deepest traceback frame identifies the failed assertion's source location.
Key Takeaways
- A false assert condition raises AssertionError immediately and stops the current function.
- A decorator sits between the caller and the wrapped function, allowing it to catch, log, and handle exceptions.
- A retry re-executes the entire wrapped function rather than only the failed line.
- The deepest traceback frame, listed last, identifies where the exception originated.
- Side effects such as database writes can happen again during a retry, so repeated execution must be considered when designing functions.
Key Takeaways
- An AssertionError begins when an assert condition is False.
- The decorator intercepts the exception after the wrapped function stops.
- The retry mechanism logs the failed attempt and may execute the whole function again.
- The deepest traceback frame shows the file name and line number where the assertion originated.
- Repeated execution can repeat side effects, including database writes.