Course

Python Decorator: Syntax, Usage, and Examples

A Python decorator lets you modify or extend the behavior of a function or class without changing its actual code. You wrap the function inside another function to add extra functionality like logging, authentication, or performance tracking.

How to Use a Decorator in Python

A decorator in Python follows a simple structure:

python
def my_decorator(func): def wrapper(): print("Something before the function runs.") func() print("Something after the function runs.") return wrapper @my_decorator def say_hello(): print("Hello!") say_hello()

How Decorators Work

  • The my_decorator function takes another function (func) as an argument.
  • Inside it, wrapper() runs extra code before and after calling func().
  • When you use @my_decorator before say_hello(), it automatically passes say_hello() into my_decorator() and replaces it with wrapper().

Using Decorators with Function Arguments

Functions often take arguments, and decorators need to handle them too. You can use *args and **kwargs to make sure the decorator works with any function.

python
def log_function_call(func): def wrapper(*args, **kwargs): print(f"Calling function {func.__name__} with arguments {args} {kwargs}") return func(*args, **kwargs) return wrapper @log_function_call def add(a, b): return a + b print(add(3, 5)) # Output: Calling function add with arguments (3, 5) {} \n 8

When to Use Python Decorators

Decorators are useful when you need to:

  1. Modify function behavior without changing its code
    • Example: Automatically logging function calls.
  2. Reuse code across multiple functions
    • Example: Enforcing authentication in web applications.
  3. Improve readability and maintainability
    • Example: Making functions cleaner by handling repetitive logic separately.

Examples of Python Decorators

Measuring Execution Time

A timer decorator lets you see how long a function takes to run.

python
import time def timer(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} executed in {end - start:.5f} seconds") return result return wrapper @timer def slow_function(): time.sleep(2) slow_function() # Output: slow_function executed in 2.000xx seconds

Passing Arguments to a Decorator

Sometimes, you want to customize how a decorator works. Instead of modifying the decorator function directly, you can use an extra function level.

python
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): for _ in range(n): func(*args, **kwargs) return wrapper return decorator @repeat(3) def greet(): print("Hello!") greet() # Output: "Hello!" printed 3 times

Using a Class as a Decorator

You can also create decorators using classes. This approach gives you more flexibility, especially when you need to store state between function calls.

python
class CountCalls: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 print(f"{self.func.__name__} has been called {self.count} times") return self.func(*args, **kwargs) @CountCalls def say_hello(): print("Hello!") say_hello() say_hello()

Learn More About Python Decorators

The @property Decorator

The @property decorator lets you define getter methods without explicitly calling them. You use it to control how a class’s attributes are accessed.

python
class Person: def __init__(self, name): self._name = name @property def name(self): return self._name p = Person("Alice") print(p.name) # Output: Alice

The @lru_cache Decorator (Memoization)

Python’s functools.lru_cache decorator caches results so that repeated function calls with the same arguments don’t recompute the results.

python
from functools import lru_cache @lru_cache(maxsize=3) def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) print(fib(10)) # Output: 55

Using a Lambda Function in a Decorator

You can simplify some decorators with lambda functions. This is useful for short, single-expression decorators.

python
def double_return(func): return lambda *args, **kwargs: func(*args, **kwargs) * 2 @double_return def square(x): return x * x print(square(3)) # Output: 18 (3*3, doubled)

A Custom Cache Decorator

Instead of using @lru_cache, you can build your own caching decorator.

python
def cache_decorator(func): cache = {} def wrapper(*args): if args in cache: print("Fetching from cache") return cache[args] result = func(*args) cache[args] = result print("Storing in cache") return result return wrapper @cache_decorator def add(a, b): return a + b print(add(2, 3)) # Output: Storing in cache \n 5 print(add(2, 3)) # Output: Fetching from cache \n 5

Using Decorators for Authentication

In web applications, decorators help enforce authentication.

python
def requires_login(func): def wrapper(user): if not user.get("logged_in"): print("Access Denied: Please log in") return return func(user) return wrapper @requires_login def view_profile(user): print(f"Welcome, {user['name']}!") user1 = {"name": "Alice", "logged_in": True} user2 = {"name": "Bob", "logged_in": False} view_profile(user1) # Output: Welcome, Alice! view_profile(user2) # Output: Access Denied: Please log in

Chaining Multiple Decorators

You can stack multiple decorators on a single function. Python applies them from bottom to top.

python
def uppercase(func): def wrapper(): return func().upper() return wrapper def exclaim(func): def wrapper(): return func() + "!" return wrapper @uppercase @exclaim def greet(): return "hello" print(greet()) # Output: HELLO!

Best Practices When Using Decorators

  • Use functools.wraps()

    When defining a decorator, use @functools.wraps(func) to preserve the original function’s name and docstring.

    python
    from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
  • Keep decorators simple

    If a decorator becomes too complex, consider refactoring it into multiple functions or a class.

  • Document your decorators

    If you write custom decorators for a project, add comments or docstrings to explain what they do.

Python decorators let you modify functions dynamically, making them powerful tools for writing cleaner, reusable code. You can use them for logging function calls, enforcing security, or improving performance, decorators help you keep your code DRY (Don’t Repeat Yourself) while adding flexibility.