Call Counter Decorator
Write a decorator @count_calls that tracks and prints how many times the function it wraps has been called, then apply it to a function that greets someone.
Why not a timing decorator? measuring and printing actual elapsed time produces a different number on every run, so it can never be checked by an automated judge against one exact expected output. Counting calls tests exactly the same decorator mechanics (wrapping, args/*kwargs, calling the original function, returning its result) in a way that's fully deterministic.
Approach: the decorator keeps a counter in its enclosing scope (a closure), increments it and prints a message on every call, then calls through to the original function.
Input: Two lines: how many times to call the function, then the name to greet.
Output: For each call: a Call <n> to greet line, followed by the greeting itself.
3 Aditi
Call 1 to greet Hello, Aditi! Call 2 to greet Hello, Aditi! Call 3 to greet Hello, Aditi!
- 1 <= number of calls <= 100
Hint 1
A dictionary (or a nonlocal variable) inside count_calls lets the wrapper remember the count between calls — this is the closure at work.
Hint 2
func.__name__ gives you the wrapped function's name as a string.
Hint 3
Don't forget to actually call func(*args, **kwargs) and return its result from wrapper — a decorator that forgets this breaks the function it wraps.
count_calls keeps a counter dictionary alive across calls via closure — the wrapper function increments it, prints the call number and function name, then calls through to the original greet(args, *kwargs) so its own behavior (and return value) is preserved. Every call to the decorated greet() therefore prints one extra 'Call N to greet' line before the greeting itself.