Decorators in Python (Beginner Introduction)
Decorators can look confusing when you first see code like @something above a function.
This page explains the basic idea in simple terms. You will learn what a decorator is, why it is useful, how the @ syntax works, and how to read simple decorator examples without getting into advanced patterns.
If decorators still feel unclear, it helps to first understand Python functions and function parameters and arguments.
def say_hello():
print("Hello")
def simple_decorator(func):
def wrapper():
print("Before function")
func()
print("After function")
return wrapper
say_hello = simple_decorator(say_hello)
say_hello()
Use this small example to understand the main idea: a decorator takes a function, wraps extra behavior around it, and returns a new function.
What this page is for
This page is meant for beginners who:
- Have seen
@decoratorsyntax and want a simple explanation - Want to understand the core idea before learning advanced patterns
- Need to see that decorators are just functions that modify other functions
What a decorator is
A decorator is a function that takes another function as input.
Then it returns a new function. That new function usually adds some extra behavior.
In simple terms:
- A decorator receives a function
- It creates a wrapper function
- The wrapper can do something before or after the original function
- The decorator returns the wrapper
The original function can still run inside the wrapper.
This is useful because you can reuse the same extra behavior in many places instead of repeating code.
Why decorators are useful
Decorators are useful when you want to add the same kind of behavior to multiple functions.
Common uses include:
- Running code before a function starts
- Running code after a function finishes
- Logging function calls
- Checking permissions or conditions
- Measuring how long a function takes
You do not need advanced projects to understand the benefit. Even a simple message before and after a function shows the idea clearly.
Understand the function-wrapping idea first
Before using the @ symbol, it helps to understand the manual version.
Look at this example:
def say_hello():
print("Hello")
def simple_decorator(func):
def wrapper():
print("Before function")
func()
print("After function")
return wrapper
say_hello = simple_decorator(say_hello)
say_hello()
What happens here
say_hellois a normal functionsimple_decoratortakes a function as input- Inside it,
wrapper()is created wrapper()prints a message, calls the original function, and prints another message- The decorator returns
wrapper - This line replaces the old function reference:
say_hello = simple_decorator(say_hello)
After that line, say_hello no longer refers to the original function directly.
It now refers to the wrapped version.
Expected output
Before function
Hello
After function
The key line inside the wrapper is:
func()
That line runs the original function. If you remove it, the original function will not run.
How the @ syntax works
The @ syntax is just a shorter way to apply a decorator.
This code:
def say_hello():
print("Hello")
def simple_decorator(func):
def wrapper():
print("Before function")
func()
print("After function")
return wrapper
say_hello = simple_decorator(say_hello)
can be written as:
def simple_decorator(func):
def wrapper():
print("Before function")
func()
print("After function")
return wrapper
@simple_decorator
def say_hello():
print("Hello")
say_hello()
A good mental model is:
my_function = my_decorator(my_function)
So when you see:
@my_decorator
def my_function():
pass
you can read it as:
- Create
my_function - Pass it into
my_decorator - Store the returned function back in
my_function
The @ form is cleaner, but it does not change the basic idea.
A minimal beginner example
Here is a small decorator example with clear output:
def simple_decorator(func):
def wrapper():
print("Starting...")
func()
print("Done.")
return wrapper
@simple_decorator
def greet():
print("Hello")
greet()
Output
Starting...
Hello
Done.
Why this works
greet()is the original function@simple_decoratorpassesgreetinto the decorator- The decorator returns
wrapper - Calling
greet()now really callswrapper()
Inside wrapper(), this line runs the original function:
func()
That is the most important line in a basic decorator.
Decorators and function arguments
A common beginner problem is that the decorator works for functions with no arguments, but breaks for functions that need values.
For example, this function needs a name:
def greet(name):
print(f"Hello, {name}")
If your wrapper does not accept arguments, it will fail.
Better version with *args and **kwargs
def simple_decorator(func):
def wrapper(*args, **kwargs):
print("Before function")
result = func(*args, **kwargs)
print("After function")
return result
return wrapper
@simple_decorator
def greet(name):
print(f"Hello, {name}")
greet("Maya")
Output
Before function
Hello, Maya
After function
What *args and **kwargs mean
In simple terms:
*argsmeans “accept any positional arguments”**kwargsmeans “accept any keyword arguments”
This makes the wrapper more flexible.
If function arguments feel unclear, review function parameters and arguments in Python or the glossary pages for parameter and argument.
Common beginner confusion
These points often cause problems:
- A decorator does not only run once forever. The wrapper runs each time the function is called.
- The decorator returns a function.
- The wrapper must call the original function if you want the original behavior to happen.
- Using
@is optional. It is only a cleaner syntax.
It also helps to remember that functions are values in Python. You can pass them into other functions, return them, and store them in variables. If that idea feels new, start with what a function is in Python.
When beginners should use decorators
For most beginners, decorators are more important to read than to write at first.
A good approach is:
- First learn normal functions
- Then learn nested functions
- Then read simple decorators
- Then try writing very small ones yourself
It is fine to avoid advanced topics for now, such as:
- Decorator factories
- Multiple stacked decorators
- Metadata preservation tools
Learn the core pattern first. Later, you will see decorators in real code, libraries, and sometimes in class-related code or module-based projects.
Natural next steps
If decorators still feel confusing, go back to the basics first.
Helpful next steps:
- Review Python functions explained
- Learn return values in Python functions
- Practice how to create a simple function in Python
- Learn Python modules explained later, since decorators often appear in larger projects
Common mistakes
Here are some common causes of decorator confusion:
- Trying to learn decorators before understanding functions
- Not realizing a function can be passed as a value
- Forgetting to return the wrapper function from the decorator
- Forgetting to call the original function inside the wrapper
- Writing a wrapper with no parameters for a function that needs arguments
Example: forgetting to return the wrapper
This is wrong:
def bad_decorator(func):
def wrapper():
print("Before")
func()
print("After")
This decorator returns None because it never returns wrapper.
Correct version:
def good_decorator(func):
def wrapper():
print("Before")
func()
print("After")
return wrapper
Example: forgetting to call the original function
This code does not run the original function:
def bad_decorator(func):
def wrapper():
print("Before")
print("After")
return wrapper
Correct version:
def good_decorator(func):
def wrapper():
print("Before")
func()
print("After")
return wrapper
Simple debugging checks
If you are experimenting and something feels wrong, these can help:
print(my_function)
print(type(my_function))
help(my_function)
print(result)
These are basic checks that help you see what your function variable now refers to and what value was returned.
FAQ
What is a decorator in Python in simple terms?
It is a function that changes or extends another function without rewriting that function.
Do I have to use the @ symbol?
No. You can call the decorator manually. The @ syntax is just a shorter and cleaner form.
Should beginners use decorators right away?
Usually no. First understand functions, arguments, and nested functions. Then decorators become much easier.
Why does a decorator return another function?
Because the new returned function is the wrapped version that adds extra behavior.
Why does my decorator fail when the function has arguments?
Your wrapper may not accept parameters. Use *args and **kwargs to forward arguments to the original function.