A Retry Decorator, Step by Step
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
- @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.
- Calling save_reading(42) runs the wrapper, which loops over attempts and calls the original function inside try.
- A false assert raises AssertionError immediately; the original stops and the exception moves up to the wrapper's except block.
- The wrapper logs the failed attempt and starts a new, complete execution of the original: side effects before the assert (saved.append) happen again.
- 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.
Loading the simulation…