Concepts / Decorators and Function Wrapping

Decorators and Function Wrapping

When the original function contains an assert statement whose condition is False, Python raises an AssertionError immediately.

  • Programming
Interactive lab

Try it: A Retry Decorator, Step by Step

What a decorator does: @retry(max_attempts) turns a function into a wrapper that calls the original. When the original's assertion fails, the wrapper catches the AssertionError, logs the attempt and runs the WHOLE function again (repeating its side effect) until it succeeds or runs out of attempts, then re-raises (with a traceback whose deepest frame is the assert) or returns a default.

How it works

  1. @retry(max_attempts=3) is evaluated first: retry(3) returns decorate; decorate(save_reading) returns wrapper; the name save_reading is bound to the wrapper.
  2. Calling save_reading(42) runs the wrapper, which loops over attempts and calls the original function inside try.
  3. A false assert raises AssertionError immediately; the original stops and the exception moves up to the wrapper's except block.
  4. The wrapper logs the failed attempt and starts a new, complete execution of the original: side effects before the assert (saved.append) happen again.
  5. After the last allowed attempt the wrapper's design decides: raise last_error (the traceback's deepest frame is the assert line; the wrapper frames only caught and re-raised it) or return None.

Default run (36 steps): Line 1: def retry(max_attempts): creates the decorator factory and binds the name retry. Nothing inside it runs yet. … The program finished: the wrapper returned 'saved' from attempt 3. The side effect ran 3 times for one reading: a retried function should be idempotent.

Simplified: One fixed program; you choose the per-attempt sensor results, max_attempts (1-5), whether the write comes before or after the check, and raise vs return None. Your settings are never executed: the lab's model reproduces CPython 3.12's line-by-line execution, printed output and traceback text for all 640 possible programs (checked against real runs). The "database" is a Python list.

Educational simulation

Loading the simulation…

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?

  • The assertion is ignored and the function continues

  • An AssertionError is raised and the current function stops

  • Only the failed line is executed again automatically

  • The caller receives a successful result immediately

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.

Condition trueexecution continuesAssertionErrorcurrent function stopsDecorator handlermay catch exceptionCondition Falseassertion failsraisespropagates to
What changes when an assert condition evaluates to False, and how is the failure passed to the surrounding decorator?

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.

CallerRetry decoratorWrapped functionResult or exceptioncallsinvokesreturns or raisesreturns or propagates
How does control move from the caller into the decorator, then into the wrapped function, and back when execution succeeds or fails?
Worked example

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. 1
    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. 2
    2. The decorator invokes the wrapped function

    The decorator passes execution to the original function so that the function can perform its work.

  3. 3
    3. The assertion condition is False

    Python raises an AssertionError immediately, and the wrapped function stops at the failed assertion.

  4. 4
    4. The error reaches the decorator

    The exception propagates through the call stack to the decorator's exception handler, which can catch it.

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

Result

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.

Wrapped functionattempt beginsAssertionErrorfunction failsException handlerdecorator catchesFailure logattempt numberMaximum attemptsretry allowed?New executionwhole functionLast exceptionmay reach callerraisespropagatesrecordschecksattempt remainslimit reachedre-executes
What happens next when the decorated function raises an AssertionError, how does the decorator catch it, and when does it re-execute the function?
Worked example

Attempt One and Attempt Two

A decorated function fails on its first execution and succeeds on its second. Predict the sequence of events.

  1. 1
    First execution

    The decorator invokes the wrapped function. The function begins, reaches a failing condition, and raises an exception.

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

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

  4. 4
    Successful return

    When the second execution succeeds, control returns through the decorator to the caller.

Result

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.

Caller frameouter callDecorator frameexception handlerWrapped functionfunction callAssertion linefile and line numbercallsinvokesreaches
How does the traceback identify the call path and pinpoint the file and line where the assertion condition became false?
Best practice

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.

Worked example

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?

  1. 1
    Identify the deepest frame

    The deepest frame is the last frame listed in the traceback.

  2. 2
    Check its location

    Look for the file name and line number shown in that frame.

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

Result

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.

Decorated functioncaller entry pointWrapperintercepts executionOriginal functionwrapped operationException handlerretry or propagateroutes toinvokesraises tocontrols next action
What contains what when a decorator replaces the original function with a wrapper, and where does the original function remain accessible?

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

  • Common mistake

    Assuming a false assertion is ignored

    What it looks like

    The function continues after an assert condition evaluates to False.

    Why it fails

    A false assert condition raises an AssertionError immediately and stops the current function.

    Do this instead

    Trace the AssertionError from the assertion to the surrounding exception handler.

  • Common mistake

    Assuming the decorator is the source of every failure

    What it looks like

    The traceback contains a decorator frame, so the decorator must contain the faulty assertion.

    Why it fails

    Decorator frames are part of the call stack, but the deepest frame shows where the exception actually originated.

    Do this instead

    Inspect the last traceback frame and use its file name and line number to find the failed assertion.

  • Common mistake

    Assuming a retry resumes at the failed line

    What it looks like

    After an assertion fails, only that assertion is evaluated again.

    Why it fails

    A retry re-executes the entire wrapped function.

    Do this instead

    Start the trace at the beginning of a new function execution and account for repeated side effects.

  • Common mistake

    Assuming every retry eventually succeeds

    What it looks like

    The decorator always returns a successful result after catching an exception.

    Why it fails

    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.

    Do this instead

    Check what the decorator does when the maximum attempt count is reached.

Control-Flow Practice

MEDIUM

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

Practice Solution

Continue the trace after the first AssertionError is caught by a retry decorator.

  1. 1
    1. Start a new full execution

    The decorator invokes the wrapped function again from the beginning.

  2. 2
    2. Observe the next outcome

    The wrapped function either succeeds or raises another exception during this new execution.

  3. 3
    3. Handle another failure if necessary

    If the new execution fails, the decorator catches and logs that failure with its attempt number.

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

Result

The control flow repeatedly cycles through full function execution, exception handling, logging, and an attempt-limit decision.

Trace Summary

  1. 1
    A false assert condition raises an AssertionError immediately and stops the current function.
  2. 2
    A decorator sits between the caller and the wrapped function, so it can intercept exceptions from the wrapped operation.
  3. 3
    A retry decorator can log a failed attempt and re-execute the entire wrapped function up to a maximum number of attempts.
  4. 4
    The deepest traceback frame, listed last, identifies where the exception originated; the file name and line number pinpoint the failed assertion.
  5. 5
    Retries repeat the complete operation, so side effects can happen again on every attempt.

At a glance

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

Related concepts

More concepts starting with D