============================================================
 DECORATORS — FUNCTION TIMING & LOGGING - Demo Run
============================================================

--- Closure example ---
  double(5) = 10, triple(5) = 15  (each remembers its own 'factor')

--- @timer ---
    [timer] slow_add took 50.203ms
  slow_add(3, 4) = 7

--- @log_calls ---
    [log] Calling greet(args=('Ava',), kwargs={})
  Result: Hello, Ava!

--- functools.wraps preserving identity ---
  slow_add.__name__ = slow_add  (would be 'wrapper' without functools.wraps)

--- @retry(times=3) decorator factory ---
    [retry] Attempt 1/3 failed: Simulated transient failure
    [retry] Attempt 2/3 failed: Simulated transient failure
  Final result: Success on 3rd attempt

--- Stacked decorators (@timer + @log_calls) ---
    [log] Calling stacked_decorators(args=(21,), kwargs={})
    [timer] stacked_decorators took 0.047ms
  Result: 42

--- @require_positive validation ---
  square_root_approx(16) = 4.0
  Correctly rejected: square_root_approx requires a positive value, got -4

============================================================
 Assignment: @cache_result memoization
============================================================
  slow_fibonacci(28) = 317811
  First call:  0.027ms
  Second call: 0.000ms (cached -- should be much faster)
  Cache speedup confirmed: True
