Decorators and Function Wrapping
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.
The First Failure
A decorated function has an extra layer between its caller and its original body. That layer can observe an exception, record what happened, and decide whether to let the exception reach the caller or to execute the function again. When the original function contains an assert statement whose condition is False, Python raises an AssertionError immediately. The current function stops at that point, and the error moves 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: An AssertionError is raised and the current function stops.
The error then propagates through the call stack. If a retry decorator is handling exceptions, that decorator can catch the AssertionError before it reaches the caller.
From Assertion to Exception
An assert statement checks a condition at a particular point in a function. If the condition is true, execution can continue past the assertion. If the condition is False, Python raises an AssertionError immediately. This does not merely mark the condition as unsuccessful: it stops the current function and begins exception propagation.
The decorator does not change the fact that the assertion fails. Instead, it can change what happens after the failure. Its exception-handling layer may catch the AssertionError, log the failed attempt, and choose whether to retry or propagate the exception.
The Decorator Boundary
A retry decorator sits between the caller and the wrapped function. The caller starts the decorated operation, the decorator invokes the wrapped function, and the wrapped function either completes or raises an exception. On success, control returns through the decorator to the caller. On failure, the decorator's exception handler gets an opportunity to log the failure and decide what happens next.
A Failed Assertion Inside a Wrapper
Trace the control flow when a caller starts a decorated function, the function reaches a false assertion, and the decorator catches the resulting AssertionError.
1. The caller starts the decorated operation: Control first enters the decorator layer rather than moving directly from the caller to the original function body.
2. The decorator invokes the wrapped function: The decorator passes execution to the original function so that the function can perform its work.
3. The assertion condition is False: Python raises an AssertionError immediately, and the wrapped function stops at the failed assertion.
4. The error reaches the decorator: The exception propagates through the call stack to the decorator's exception handler, which can catch it.
5. The decorator chooses the next action: A retry design can log the failed attempt and re-execute the entire wrapped function, subject to its maximum number of attempts.
The decorator is the control boundary that can intercept the AssertionError before the caller receives it.
Retrying the Whole Operation
A retry decorator can catch exceptions, including AssertionError, log the failure together with the attempt number, and automatically re-execute the function up to a maximum number of attempts. The retry is a new execution of the entire wrapped function. It is not a jump back to only the assertion line.
Attempt One and Attempt Two
A decorated function fails on its first execution and succeeds on its second. Predict the sequence of events.
First execution: The decorator invokes the wrapped function. The function begins, reaches a failing condition, and raises an exception.
Failure handling: The decorator catches the exception and logs a message identifying the failed attempt, such as the first attempt out of a configured maximum.
Second execution: The decorator invokes the entire wrapped function again. The function starts from the beginning of its wrapped execution rather than from only the failed line.
Successful return: When the second execution succeeds, control returns through the decorator to the caller.
The observable sequence is function execution, exception, decorator logging, full function re-execution, and successful return.
Reading the Traceback
When an assertion fails, Python generates a traceback. The traceback identifies the file name and line number associated with the failed assertion and shows the function where the failure occurred. It also displays the call path that led to the failure.
Read the traceback from the bottom upward. The deepest frame, listed last, identifies where the exception actually originated. Decorator frames belong to the call stack, but they are not necessarily the source of the error. Use the file name and line number in the deepest frame to locate the failed assertion in the source code.
Separating the Wrapper from the Failure
A traceback contains a caller frame, a decorator frame, and a final frame showing the wrapped function's assertion line. Which frame identifies the original failure?
Identify the deepest frame: The deepest frame is the last frame listed in the traceback.
Check its location: Look for the file name and line number shown in that frame.
Distinguish handling from origin: The decorator frame explains where the exception was intercepted or handled. The deepest frame identifies where the assertion actually became false.
The final, deepest traceback frame points to the assertion's source location.
What the Wrapper Contains
Function wrapping describes the relationship in which a decorator places a wrapper layer around an original function. The wrapper becomes the path used by the caller, while the original function remains the operation that the wrapper invokes. This arrangement lets the decorator intercept execution before and after the wrapped function, including the moment when the wrapped function raises an exception.
The important mental model is not that the decorator removes the original function's behavior. It inserts a control layer around that behavior. The caller enters through the wrapper, and the wrapper decides how to invoke the original operation and how to respond if that operation fails.
Mistakes in Control Flow
Assuming a false assertion is ignored
A false assert condition raises an AssertionError immediately and stops the current function.
Fix:
Trace the AssertionError from the assertion to the surrounding exception handler.Assuming the decorator is the source of every failure
Decorator frames are part of the call stack, but the deepest frame shows where the exception actually originated.
Fix:
Inspect the last traceback frame and use its file name and line number to find the failed assertion.Assuming a retry resumes at the failed line
A retry re-executes the entire wrapped function.
Fix:
Start the trace at the beginning of a new function execution and account for repeated side effects.Assuming every retry eventually succeeds
A retry process is limited by a maximum number of attempts. After the limit, the decorator may raise the last exception or return a default value, depending on its design.
Fix:
Check what the decorator does when the maximum attempt count is reached.
Control-Flow Practice
A retry decorator allows several attempts. On the first attempt, the wrapped function reaches a false assertion. The decorator catches the AssertionError and logs the failed attempt. Before looking at the answer, write the next four events in order.
Hints
- The decorator retries the entire wrapped function, not only the assertion line.
- The next execution starts at the beginning of the wrapped function.
- The decorator continues checking the attempt limit after each failure.
Practice Solution
Continue the trace after the first AssertionError is caught by a retry decorator.
1. Start a new full execution: The decorator invokes the wrapped function again from the beginning.
2. Observe the next outcome: The wrapped function either succeeds or raises another exception during this new execution.
3. Handle another failure if necessary: If the new execution fails, the decorator catches and logs that failure with its attempt number.
4. Compare with the maximum: The decorator retries again while attempts remain; after the limit, its design determines whether the last exception is raised or a default value is returned.
The control flow repeatedly cycles through full function execution, exception handling, logging, and an attempt-limit decision.
Trace Summary
- A false assert condition raises an AssertionError immediately and stops the current function.
- A decorator sits between the caller and the wrapped function, so it can intercept exceptions from the wrapped operation.
- A retry decorator can log a failed attempt and re-execute the entire wrapped function up to a maximum number of attempts.
- The deepest traceback frame, listed last, identifies where the exception originated; the file name and line number pinpoint the failed assertion.
- Retries repeat the complete operation, so side effects can happen again on every attempt.
Key Takeaways
- A false assertion changes normal execution into AssertionError propagation.
- The decorator is the interception layer that can catch, log, retry, or propagate the exception.
- Automatic retry begins a new execution of the whole wrapped function.
- The last traceback frame is the best place to locate the original assertion failure.
- Always consider repeated side effects and the decorator's behavior after the retry limit.