DEV Community

Nattar Kani Murugan
Nattar Kani Murugan

Posted on

Day 3: Learning Decorators by Building My Own Python Library

Okay, Nattar. Day 3.

Yesterday was comprehensions and generators. Today, I went down a different Python rabbit hole: Decorators

I had seen decorators before, but seeing @something above a function and actually understanding what happens underneath are two very different things.

So instead of just reading about them, I decided to build a small decorator library.

First... what is a decorator?

The simplest way I can explain it to myself:

A decorator lets me add extra behaviour to a function without changing the original function.

For example:

@log_calls
def add(a, b):
    return a + b
Enter fullscreen mode Exit fullscreen mode

The add() function still does what it was supposed to do.

The decorator simply adds something around it. And that's the idea I wanted to practice.

My five decorators

1. log_calls

This one logs the function name and the arguments passed to it.

def log_calls(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
        print(
            f"Calling function {func.__name__} "
            f"with arguments {args} and keyword arguments {kwargs}"
        )
        result = func(*args, **kwargs)
        return result

    return wrapper
Enter fullscreen mode Exit fullscreen mode

This helped me understand *args and **kwargs a little better too.

2. timeit

I wanted to know how long a function takes to execute.

start = time.time()

result = func(*args, **kwargs)

end = time.time()
elapsed = end - start
Enter fullscreen mode Exit fullscreen mode

So instead of adding this timing logic inside every function, I can simply use:

@timeit
def my_function():
    ...
Enter fullscreen mode Exit fullscreen mode

3. retry

What happens if a function fails?

Try again. That's what this decorator does.

for i in range(3):
    try:
        result = func(*args, **kwargs)
        return result
    except Exception as e:
        error = e
        print(f"Attempt {i+1} failed..")
Enter fullscreen mode Exit fullscreen mode

If the function fails, it gets three attempts before the error is raised.

4. cache

This one was interesting. If I call a function with the same arguments multiple times, why calculate the same result again?

cache = {}

key = (args, tuple(kwargs.items()))

if key in cache:
    return cache[key]

result = func(*args, **kwargs)

cache[key] = result
return result
Enter fullscreen mode Exit fullscreen mode

Now the result can be reused when the same arguments are passed again. This was my first hands-on experience with implementing caching rather than just hearing about it.

5. validate

Finally, I wanted to experiment with validating function arguments using type annotations.

I used inspect.signature() to bind the arguments and then checked their types:

expected_type = annotations.get(name)

if not isinstance(value, expected_type):
    raise TypeError(...)
Enter fullscreen mode Exit fullscreen mode

So I could write something like:

@validate
def add(a: int, b: int):
    return a + b
Enter fullscreen mode Exit fullscreen mode

And the decorator checks whether the arguments are actually integers before running the function.


And then I thought...

Why stop at five .py files?

I wanted to try turning these decorators into an installable Python package. So I created a package structure, added the necessary package configuration, and tried installing it locally.

Something like:

pip install .
Enter fullscreen mode Exit fullscreen mode

And then I could import the decorators into another project. That was probably my favourite part of today's challenge.

What did I learn today?

Today I got hands-on with:

  • Decorators
  • Wrapper functions
  • @wraps
  • *args and **kwargs
  • Caching
  • Logging
  • Retry logic
  • Function timing
  • Type validation
  • inspect.signature()
  • Python package structure
  • Making a local package installable

Today started with: "Let's learn decorators."

And ended with: "Wait... I actually built a package."

Not bad. Still a long way to go.

But that's exactly why I started this 10 Week AI Challenge. I'm not trying to become an AI engineer in ten days. I'm trying to build the foundation that will help me become one.

Day 3: Done. ✅

Three days in. Three different learning experiences.

See you on Day 4. 🚀

I've added my decorator library to GitHub as part of my 10 Week AI Challenge.

👉 View the Decorator Library on GitHub

Keep going, Nattar.

Top comments (0)