{}const=>[]async()letfn</>var
DevelopmentPython

Decorators in Python: expanding functionality without changing the code

Learn how to use Python decorators to elegantly extend functionality. We analyze practical examples: logging, caching, measuring execution time and checking access rights. Clear explanations with code examples for beginners and experienced developers.

К

Kodik

Author

5 min read

Imagine that you are developing a web application and you need to log each API call, check user access rights, and measure the execution time of functions. You can, of course, copy the same code into each function, but there is a more elegant way — decorators. This is one of the most powerful Python tools that allows you to modify the behavior of functions without changing their source code.

What are decorators

A decorator is a function that takes another function as an argument and returns a new function with enhanced functionality. Sounds complicated? In fact, everything is easier than it seems.

def my_decorator(func):
    def wrapper():
        print("Something happens before the function is called")
        func()
        print("Something happens after calling the function")
    return wrapper

@my_decorator
def say_hello():
    print("Hi!")

say_hello()

Result of execution:

Что-то происходит до вызова функции
Привет!
Что-то происходит после вызова функции

The @ symbol is Python's syntactic sugar. The entry @my_decorator before the function is equivalent to say_hello = my_decorator(say_hello).

🔥 100,000+ students already with us

Tired of reading theory?
Time to code!

Kodik — an app where you learn to code through practice. AI mentor, interactive lessons, real projects.

🤖 AI 24/7
🎓 Certificates
💰 Free
🚀 Start learning
Joined today

Decorators with arguments

But what if our function accepts parameters? To do this, use *args and **kwargs:

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"Hi, {name}!")

greet("Alexey")

Conclusion:

Привет, Алексей!
Привет, Алексей!
Привет, Алексей!

Here we have created a decorator with a parameter that repeats the function call a specified number of times.

Practical examples of decorators

Measurement of execution time

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"The {func.__name__} function was executed in {end - start:.4f} seconds")
        return result
    return wrapper

@timer
def heavy_computation():
    time.sleep(2)
    return "Done"

heavy_computation()

Please note @wraps(func) — this is a built-in decorator from the functools module that saves the metadata of the original function (name, documentation, and so on).

Caching results

def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(100))  # Executed instantly thanks to caching

Checking access rights

def require_auth(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get('is_authenticated'):
            raise PermissionError("Authorization required")
        return func(user, *args, **kwargs)
    return wrapper

@require_auth
def delete_account(user, account_id):
    print(f"Account {account_id} has been deleted")

user = {'is_authenticated': True, 'username': 'admin'}
delete_account(user, 123)

Decorator chains

Decorators can be combined by applying several to one function:

@timer
@memoize
def complex_calculation(x, y):
    time.sleep(1)
    return x ** y

# The first call will take ~1 second
complex_calculation(2, 10)

# The second call will be instant (the result is cached)
complex_calculation(2, 10)

Decorators are applied from bottom to top, that is, first memoize, then timer.

Classes as decorators

Decorators don't have to be functions. You can use classes:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Call #{self.count} of the {self.func.__name__} function"_}")
        return self.func(*args, **kwargs)

@CountCalls
def process_data():
    print("Processing data...")

process_data()
process_data()
process_data()

Built-in Python decorators

Python provides several useful built-in decorators:

  • @property — turns a method into a class attribute

  • @staticmethod — creates a static method

  • @classmethod — creates a class method

  • @functools.lru_cache — caching with size limit

  • @dataclass — automatic creation of class methods

from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_function(param):
    # Complex calculations
    return param * 2

When to use decorators

Decorators are ideal for tasks that need to be applied to many functions: logging, input validation, exception handling, access control, caching, performance measurement, retry logic for network errors. They help to comply with the DRY (Don't Repeat Yourself) principle and make the code cleaner and clearer.

Potential problems

Despite all the power of decorators, you need to remember a few points. Excessive use of decorators can complicate code debugging. Decorators add small overheads to performance. It is important to always use @wraps to save the function metadata. Chains of multiple decorators can reduce code readability.

Summary

Decorators are an elegant way to modify and extend the functionality of code without changing it. They are widely used in popular frameworks like Flask and Django, and understanding how they work will greatly simplify your life as a developer. Start with simple decorators for logging or measuring time, and gradually you will find more and more situations where they will be useful.

Want to master decorators and other advanced Python features in practice?

Appendix Code created specifically so that you can learn programming at a comfortable pace, with clear explanations in Russian and real-life examples. We analyze complex topics in simple language — from the basics to professional techniques. Each lesson is structured so that you don't just memorize the syntax, but understand how to apply your knowledge in real projects.

And if you have any questions during the training, you are welcome to our Telegram channel! This is a lively community of developers where you can ask any question and get a detailed answer. Our atmosphere is friendly and informative — every day we analyze the top topics in development, share our experience and help each other grow. Join Kodik — start your programming journey with those who really understand how to make learning effective and interesting!

🎯Stop procrastinating

Liked the article?
Time to practice!

In Kodik, you don't just read — you write code immediately. Theory + practice = real skills.

Instant practice
🧠AI explains code
🏆Certificate

No registration • No card