If you have learned Python to a certain extent, you must have come across the following piece of code:
@log
def hello():
print("Hello World")
When you first see <span>@log</span>, many people have the same feeling:
—— This looks advanced, but I have no idea what it does.
Some people get stuck; some memorize it; and others use it while always wondering: What is the essence of this thing? How can it add functionality to a function without me noticing?

In fact, decorators are not mysterious at all; they are neither magic nor special syntax. Their essence can be summarized in one sentence:
A decorator is a “wrapping technique”: using one function to wrap another function.
After reading this article, you will be able to thoroughly understand decorators. Not only will you grasp their essence, but you will also know why they can change the organization of a Python project.
01 The Original Motivation for Decorators: Adding Functionality Without Modifying Code
All great syntax is not designed out of thin air. They must originate from very practical pain points.
Imagine you have written 20 functions, and now your boss says:
“All 20 functions need to print a message before execution—‘Execution started.’”
What would you do?
The most primitive way would be:
def func():
print("开始执行了")
...
Then you would copy and paste it 20 times.
But what would this lead to?
- Code redundancy
- Copy-pasting is prone to errors
- If you need to change the logic, you have to change it 20 times
- The overall code looks very messy
Programmers hate “repetition” the most.
So someone thought: Is there a way to do something before executing the function without modifying its body?
After thinking it over, there’s only one solution:
Wrap it in a shell, write the “pre-logic” inside the shell, and then call the original function from within the shell.
Thus, the prototype of decorators emerged.
This is the real reason for the emergence of decorators; it is not about syntax, but about the demand itself.
02 The Essence of Decorators: Functions that Accept Functions and Return Functions
You don’t need to memorize any terms, structures, or patterns. Just remember this one sentence:
Decorator = a function that accepts a function + a function that returns a function
In other words:
A decorator is essentially: a function that takes a function, wraps it, and returns a “stronger function”.
Let’s start with the simplest, most primitive way of writing:
def decorator(func):
def wrapper(*args, **kwargs):
print("函数要开始执行了")
result = func(*args, **kwargs)
print("函数执行完毕")
return result
return wrapper
See, this is the core essence of decorators:
<span>decorator</span>accepts a function<span>decorator</span>returns a new function<span>wrapper</span><span>wrapper</span>not only executes the original function but also adds pre- and post-logic
To run it, you need to manually replace it:
def hello():
print("Hello")
hello = decorator(hello)
hello()
Right? We didn’t use <span>@</span>, but the decorator is already working.
Thus, you understand a key truth:
Decorators are just “function wrappers” with no magic involved.
03 @decorator: Just Syntax Sugar
Once you understand the above, the next most important thing is this sentence:
@decorator is just syntax sugar, equivalent to: hello = decorator(hello)
This is the root of the misunderstanding that decorators are “magic”.
For example:
@decorator
def hello():
print("Hello")
Is equivalent to:
def hello():
print("Hello")
hello = decorator(hello)
It’s just a reassignment.
No black technology, no hidden rules, no compiler magic, and it’s not advanced syntax.
It’s just this action: replacing hello with decorator(hello).
Once you understand this, decorators become down-to-earth; they are no longer “advanced syntax” but a natural extension of ordinary Python.
04 Why Are Decorators So Powerful? Because They Utilize Two Powerful Weapons
Decorators are powerful not because of the “@” symbol, but because of these two foundational capabilities:
Weapon 1: Higher-Order Functions
In simple terms:
Functions can be passed as variables, returned, and assigned.
Python’s functions are first-class citizens, which makes wrapping functions exceptionally natural.
Weapon 2: Closures
<span>wrapper</span> can access <span>func</span> because Python has closures:
Inner functions can remember the variables of outer functions.
Thus, <span>wrapper</span> can remember the function that was originally supposed to be executed, no matter where it is executed.
This makes “function wrapping” possible.
Combining these two weapons gives decorators all their underlying capabilities.
05 Practical Scenarios: Why Large Projects Cannot Do Without Decorators?
When you actually start working on projects, you will find that decorators are everywhere.
✔ API Permission Checks
@check_permission("admin")
def delete_user(uid):
...
No need to write repetitive logic for each API.
✔ Request Logging
@app.get("/user")
def get_user():
return ...
Flask and FastAPI rely on decorators to implement routing.
✔ Caching
@lru_cache()
def fib(n):
...
Performance improvements by dozens of times.
✔ Automatic Retries
@retry(times=5)
def fetch_data():
...
No more need to write while True manually.
✔ Function Execution Time Statistics and Performance Monitoring
@timeit
def process():
...
Does not affect business code.
✔ Mocking in Unit Tests
Decorators can make tests cleaner.
You will find one statement:
Decorators allow the same type of logic to be “centrally managed” rather than scattered across every function.
This is a design philosophy that promotes clean, maintainable, and highly reusable code.
06 Parameterized Decorators: Essentially “Wrapping Again”
Many people get confused when they see such decorators:
@retry(times=3)
def fetch():
...
In essence, it is just one sentence:
Parameterized decorators are essentially “functions that return decorators”.
That is, it wraps another layer.
The structure is actually very simple:
def outer(times):
def decorator(func):
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapper
return decorator
The knowledge you need to remember is still:
- It still accepts a function
- It still returns a function
- The essence hasn’t changed, just an additional layer of wrapping
No need to memorize; just understand the structure.
07 The Three “Pits” of Decorators and the Underlying Logic
Pit 1: Loss of Function Information
After decoration, the function becomes a wrapper, leading to:
- Incorrect function name
- Comments are lost
- Debugging becomes inconvenient
Solution:
from functools import wraps
<span>@wraps</span> is the “official assistant” for decorators.
Pit 2: Execution Order of Multiple Decorators
Many students see:
@A
@B
def func():
pass
And think A executes first. Actually, it does not.
The order is:
func = A(B(func))
Just think of it as an “onion model”: the innermost shell (B) is closest to the function, and the outermost layer (A) takes effect last.
Pit 3: Can Decorators Not Pass Return Values?
This is due to a mistake in the wrapper. Just write:
return func(*args, **kwargs)
And the return value won’t be lost.
08 The True Value of Decorators: Changing the Way We Organize Code
The significance of decorators has never been about syntax, but about philosophy.
They represent a:
- Pluggable Logic
- Separation of Cross-Cutting Logic
- Pursuit of Decoupling, Purification, and Clarity in Code
For example: when you write a business function:
def create_order():
# Core logic
This function’s essence is simply “creating an order”.
It should not bear:
- Logging
- Performance Monitoring
- Error Retrying
- Permission Checks
- Cache Control
- Data Validation
- Cost Monitoring
All of these should be handled by the “decorator layer”.
Decorators act as a “defensive military system”, stripping away all non-core logic from business functions, allowing business logic to be clean and pure.
This is the true greatness of decorators.
09 In Summary: Decorators Are the “Plugin System” of the Python World
After reading the entire text, you should arrive at a clear conclusion:
The essence of decorators is “the plugin system for functions”.
They help you:
- Enhance functions without modifying the source code;
- Unify the management of repetitive logic;
- Keep core code pure;
- Make large projects more maintainable;
- Make code elegant, clean, and extensible.
They are not magic, but they are more powerful than magic.
Because they allow you to achieve the most flexible and extensible design capabilities with the simplest syntax.
Those who write good decorators have already stood at the dividing line between “knowing how to write Python” and “knowing how to design systems”.