Python Decorators – Syntax, Examples & How They Work

Introduction

Decorators allow developers to modify or extend the behavior of functions and methods without changing their original code. They provide a clean and reusable way to add functionality such as logging, authentication, timing, validation, caching, and access control.

For example, imagine you have multiple functions in an application and want to log whenever each function is executed. Instead of writing logging code inside every function, you can create a decorator and apply it wherever needed.

What are Python Decorators?

A decorator is a function that takes another function as an argument, adds some functionality, and returns a modified function.

In simple words:

A decorator wraps another function to extend its behavior without modifying the original function.

Example:


def decorator_function(func):
    def wrapper():
        print("Before Function")
        func()
        print("After Function")
    return wrapper

The decorator adds functionality before and after the original function executes.

Functions are First-Class Objects

To understand decorators, you must first understand that functions are first-class objects in Python.

This means functions can:

  • Be assigned to variables
  • Be passed as arguments
  • Be returned from other functions

Example:


def greet():
    print("Hello")
message = greet
message()

Output:

Hello

This capability makes decorators possible.

Syntax

General syntax:


def decorator(func):
    def wrapper():
        # Extra functionality
        func()
    return wrapper

Applying the decorator:


@decorator
def hello():
    print("Hello World")

This is equivalent to:


hello = decorator(hello)

Creating a Simple Decorator

Example:


def my_decorator(func):
    def wrapper():
        print("Before Function")
        func()
        print("After Function")
    return wrapper

Applying:


@my_decorator
def greet():
    print("Welcome")
Usage:
greet()

Output:

Before Function
Welcome
After Function

Understanding How Decorators Work

Let’s break it down:

Step 1

Original function:


def greet():
    print("Welcome")

Step 2

Decorator receives the function:


my_decorator(greet)

Step 3

Decorator returns the wrapper function.

Step 4

When called:


greet()

Python actually executes:


wrapper()

which internally calls the original function.

Decorators with Arguments

Functions often accept parameters.

Example:


def decorator(func):
    def wrapper(name):
        print("Before Function")
        func(name)
    return wrapper

Applying:


@decorator
def greet(name):
    print(
        f"Hello {name}"
    )

Usage:


greet("John")

Output:


Before Function 
Hello John

Using *args and **kwargs

To support any number of arguments, use:


*args
**kwargs

Example:


def decorator(func):
    def wrapper(
        *args,
        **kwargs
    ):
   print("Executing")
        return func(
            *args,
            **kwargs
        )
    return wrapper

Applying:


@decorator
def add(a, b):
    return a + b

Usage:


print(add(10, 20))

Output:

Executing
30

Returning Values from Decorators

Always return the original function’s result when needed.

Example:


def decorator(func):
    def wrapper(*args):
        result = func(*args)
        return result
    return wrapper

Usage:


@decorator
def multiply(a, b):
    return a * b
print(
    multiply(4, 5)
)

Output:

20

Multiple Decorators

Python allows stacking decorators.

Example:


def decorator1(func):
    def wrapper():
        print("Decorator 1")
        func()
    return wrapper
def decorator2(func):
    def wrapper():
        print("Decorator 2")
        func()
    return wrapper

Applying:


@decorator1
@decorator2
def greet():
    print("Hello")

Output:

Decorator 1 Decorator 2 Hello

Decorators are applied from bottom to top.

Decorators with Parameters

Decorators themselves can accept arguments.

Example:


def repeat(times):
    def decorator(func):
        def wrapper():
            for i in range(times):
                func()
        return wrapper
    return decorator

Usage:


@repeat(3)
def hello():
    print("Hello")

Output:

Hello
Hello
Hello

Real-Life Examples:

1. Logging Decorator


def logger(func):
    def wrapper(*args):
        print(
            f"Calling {func.__name__}"
        )
        return func(*args)
    return wrapper

Applying:


@logger
def add(a, b):
    return a + b

Usage:


print(add(5, 3))

Output:

Calling add
8

Useful for debugging applications.

2. Authentication Decorator


def authenticate(func):
    def wrapper(user):
        if user == "admin":
            return func(user)
        print("Access Denied")
    return wrapper

Applying:


@authenticate
def dashboard(user):
    print("Welcome Admin")

Usage:


dashboard("admin")

Output:

Welcome Admin

3. Execution Timer


import time
def timer(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(
            "Time:",
            end - start
        )
    return wrapper

Usage:


@timer
def task():
    time.sleep("2")
task()

Output:

Time: 2.0

(Approximate value)

Built-in Decorators in Python

Python provides several built-in decorators.

Decorator Purpose
@staticmethod Creates static methods
@classmethod Creates class methods
@property Creates getter methods
@abstractmethod Defines abstract methods

Example:


class Student:
    @staticmethod
    def show():
        print("Static Method")

The @property Decorator

Example:


class Student:
    def __init__(self, name):
        self._name = name
    @property
    def name(self):
        return self._name

Usage:


student = Student("John")
print(student.name)

Output:

John

Advantages of Decorators

Advantage Description
Reusability Use logic multiple times
Cleaner Code Reduces duplication
Better Maintenance Easier updates
Separation of Concerns Keeps code organized
Flexibility Modify functions dynamically

Disadvantages of Decorators

Disadvantage Description
Learning Curve Difficult for beginners
Debugging Complexity Wrapped functions can be harder to trace
Excessive Nesting Multiple decorators can reduce readability

Common Mistakes

1. Forgetting to Return Wrapper

Incorrect:


def decorator(func):
    def wrapper():
        pass

Correct:


return wrapper

2. Not Returning Function Result

Incorrect:


def wrapper():
    func()

Correct:


return func()

3. Ignoring Arguments

Incorrect:


def wrapper():

Correct:


def wrapper(
    *args,
    **kwargs
):

4. Misunderstanding Decorator Order

Example:


@A
@B
def func():

Equivalent to:


func = A(B(func))

5. Overusing Decorators

Not every function needs a decorator.

Use them only when they improve code organization.

Best Practices

1. Use Meaningful Names

Good:


@authenticate
@logger
@timer

Bad:


@d1 
@d2

2. Support All Arguments

Use:


*args
**kwargs

for maximum flexibility.

3. Keep Decorators Focused

One decorator should perform one task.

4. Return Function Results

Always return the original function’s output if needed.

5. Use Built-in Decorators When Available

Examples:


@property
@classmethod
@staticmethod

Conclusion

Python Decorators are a powerful feature that allows developers to modify or extend the behavior of functions and methods without changing their original code.

Mastering decorators is an essential step toward becoming an advanced Python developer and writing cleaner, more professional applications.

Related Python Tutorials