Decorators let you wrap extra behavior around a function without touching its source.
Why decorators?
Suppose we start with:
1 2 3 4
import random
deffunc_a(): print("I'm func_a, get a random number %s" % random.random())
We want to print an extra line whenever func_a runs. Editing the function directly works, but it does not scale when the same tweak applies to many functions—or if we cannot edit the source.
First attempts
Wrapping the function manually:
1 2 3
defnew_func_a(): print("I'm new code") func_a()
Works but requires one wrapper per function. Passing the original function into a helper is cleaner:
1 2 3 4 5
defadd_new_code(func): print("I'm new code") return func
new_func_a = add_new_code(func_a)
The helper runs immediately, so the extra line prints during assignment, not when the function executes. We need to return a callable that delays the work.