python decorators

Mar 8, 2026

When I first saw decorators in Python, they felt a bit strange.

That @ symbol looked unusual.

It felt like some special Python trick that experienced developers understood and beginners were not supposed to touch yet.

At least that’s how it felt to me.

But later I realized something simple.

Decorators are just functions.

A decorator is simply a function that takes another function as input and returns a function.

That’s it.

Nothing magical.

This works because in Python functions are first-class objects.
Which simply means they can be passed around like any other piece of data.

Once this clicked for me, decorators started feeling much more natural.

And actually… quite pleasant to use.


hiding small patterns

One thing I like about decorators is that they let me hide small repeating patterns.

Instead of writing the same logic again and again, I can wrap that logic in a decorator and reuse it.

A very simple example is keeping track of functions.

Imagine we want to automatically register certain functions.

Instead of manually maintaining a list, we can use a decorator.

registry = []

def register(func):
    registry.append(func)
    return func

Now we can mark functions like this:

@register
def greet():
    print("hello")

@register
def bye():
    print("goodbye")

Now those functions automatically appear in the registry.

print(registry)

Output:

[<function greet>, <function bye>]

We didn’t manually add anything.
The decorator handled the pattern for us.


the @ syntax is just shorthand

The @ symbol may look special, but it's really just syntax sugar.

This:

@register
def greet():
    print("hello")

Is exactly the same as writing:

def greet():
    print("hello")

greet = register(greet)

Python simply passes the function into another function.

Once I realized this, decorators stopped feeling mysterious.

They're just a cleaner way to write something we could already do.


a practical example: timing a function

Another place decorators are useful is measuring how long a function takes to run.

Instead of writing timing code everywhere, we can hide that logic inside a decorator.

import time

def timer(func):

    def wrapper(*args, **kwargs):
        start = time.time()

        result = func(*args, **kwargs)

        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")

        return result

    return wrapper

Now we can use it like this:

@timer
def slow_function():
    time.sleep(1)

Calling the function:

slow_function()

Output:

slow_function took 1.0001 seconds

The original function stays clean, and the timing logic lives somewhere reusable.


a simple mental model

Mar 8, 2026

Sometimes I like to think of decorators like this:

your_function → decorator → new_function

The decorator takes your function, wraps some extra behavior around it, and returns a new function.

The @ syntax just makes this transformation easier to write.


why I enjoy using decorators

When I started learning Python, decorators looked intimidating.

Now they feel like a small elegant tool.

They let me hide patterns like:

Instead of repeating that logic everywhere.

I write it once.

And then I reuse it.

Decorators felt strange when I first saw them.

But once the idea clicked, I started reaching for them quite often.

And honestly… they’re kind of fun to use.

go back to tech