Rodrigo 🐍🚀 Profile picture
Mar 17, 2023 22 tweets 7 min read Read on X
Python 🐍 decorators are often considered an “advanced” topic.

But they don't need to be hard.

Let me explain 👇 how decorators are an elegant and powerful tool. 🚀 Image
The first thing you need to understand is WHY decorators matter.

A decorator is a tool that you use to add a feature to another function.

For example, you can add caching to a function with a caching decorator.

But the, why not implement that in the function itself?
What if you need to add caching to three, four, ten, or twenty functions?

Will you implement the caching behaviour over and over?

Also, your function fulfils a purpose.

Caching would be the cherry `@` on top of the cake!

But it's not the main point of the function, right?
So, you take the caching logic and put it in a decorator.

Then, you can sprinkle the caching on top of the functions you want…

You can DECORATE your functions with caching.

Here is an example 👇

But how do you write a decorator? Image
A decorator will add functionality to a function, right?

So, it must accept a function as its argument.

Then, it does some decorating magic to that function…

And it returns the modified function.

The $1,000,000 question is: how does it modify the function?! Image
The quick answer?

It doesn't!

It cheats! 😆🤡

But it's a clever cheat.

I'll explain.
A decorator doesn't really modify the function…

It creates another one around it!

We call it a “wrapper”.

The wrapper is the one responsible for doing the extra work in coordination with the function you want to decorate.

Want a simple example?
Let's create a logging decorator.

It will print the name of the function being called.

What needs to be the behaviour of your “modified function”?

It would need to start by printing its name, and then do the original thing.

If you could just add a `print` at the beginning... Image
So, we'll create a new function.

I'm not imaginative, so I will just call it “wrapper”.

The function “wrapper” will start by printing the name of the original function.

Then, it does everything the original function would do.

How..?
It just calls the original function! 🤣

That's it, really.

Since we can't open the original function and change it…

We just do the logging before, and then call the original function.

Something like this 👇 Image
If we look at `my_decorator`, we see it actually doesn't do much.

It just creates a function called `wrapper` and it returns it.

That's it.

`wrapper` is the one doing all the work.

And even so, it isn't that much…
It prints the name of the function that was the argument to the decorator…

And then calls that very same function!

Challenge:

Modify `wrapper` to also log the return value of calling the original function.

Hint:

The template I'll show next will help.
I always use this template 👇 when writing decorators.

It covers 99%, if not 100%, of the use cases for decorators.

The template has a couple of parts that I will summarise for you. Image
First, we need to fix the issue with the ellipsis `...`.

What are they doing there?

Well, the decorator needs to work for all possible functions `func_to_decorate`.

And we can't know, beforehand, what their signatures will be…

So, what do we write in place of the `...`?
We write `*args` and `**kwargs`, because that will cover all possible use cases!

Usually, that is what you write there.

The only exceptions are when you want to decorate very specific functions with specific signatures.

That was part 1 of the template. Image
The next thing we take a look at is that mysterious `wraps`.

It has to do with how the decorators are applied and the syntax with the at sign `@`.

The at sign `@` is a shortcut to apply a decorator.

A Python decorator can be applied in two different ways 👇

But, what gives? Image
Applying a decorator actually replaces the original name.

The original name that you gave to the original function is now used to point to the `wrapper`.

Do you see the issue with this?

Not very obvious, but it simple to understand:
You lose direct access to useful things like:

👉 the original function name; and
👉 the original function signature.

So `functools.wraps` is there to make sure you don't lose that nice information.

This was part 2 of the template. Image
Next up: the decorator defines a `wrapper` and then returns it.

We have seen this was the closest we could come to actually modifying our original function.

The `wrapper` is where we implement the logic of the decorator.

(Caching, logging, … you name it!)

That's part 3. Image
We can add whatever code we want before calling our original function.

Similarly, we can add code that runs after the function was called.

To allow this, we need to save the result of the original function.

This way, we can still return the original result. ImageImage
I created a huge, final annotated decorator template for you 👇

Let me know if it is useful or if it is a mess.

As a challenge, go write a timing decorator.

It should print the execution time of the decorated functions. Image
That's it for now! 🚀

This thread took a lot of effort to write! 😵 If you enjoyed it:

👉 follow me @mathsppblog for more Python 🐍 knowledge; and
👉 retweet the tweet below to share this thread with your audience!

I'll see you around. 👋

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Rodrigo 🐍🚀

Rodrigo 🐍🚀 Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @mathsppblog

May 9, 2025
Hide private information in your Python code.

Suppose you want to print an email...

But you want to create that cool ro*****@mathspp.com effect.

This is pretty easy to achieve in Python!

All you need to do is use an f-string and use the appropriate format specifiers. Diagram showing how you can use f-strings and their format specification to redact private or sensitive information, like email addresses.  The code from the diagram:  def redact_email(email):     user, _, domain = email.partition("@")     return f"{user[:2]:*<{len(user)}}@{domain}"  print(redact_email("rodrigo@mathspp.com")) # ro*****@mathspp.com
👉 the first thing you do is use `str.partition` to grab the email “user” and the domain.

We will redact only the user (but you could also redact the domain with the same process).

The `user[:2]` shows the first two characters.

That's the “ro”.

But how do you get “ro*****”?
👉 use an f-string and the width specifier.

You want to create a field as wide as “rodrigo”:

r o _ _ _ _ _

The length of this field is `len(user)`, so you use `{len(user)}` INSIDE the format spec.

This creates a field with the correct width.
Read 6 tweets
May 18, 2023
I know `print` is the first Python 🐍 function you learned! 🚀

And yet, you don't know this about `print` 👇 Image
What you know for sure is that `print` will take an object and it will print it on the screen.

That's the basic functionality it provides: Image
Maybe you don't know that `print` can actually print multiple things!

If you pass multiple arguments, they all get printed: Image
Read 11 tweets
May 17, 2023
I'll tell you the story of a person that had the wrong name…

And how to prevent that in Python 🐍 with properties 🚀.

👇 Image
John Doe was a regular guy and when he was born, he was inserted into the government's database of people.

They created a new `Person` and added John's details: Image
John never liked his name Doe, though.

So Joe decided to change his name to Smith.

And so he did.

He updated his last name, but the government `Person` STILL had the wrong name! Image
Read 10 tweets
May 14, 2023
Opening a file to read/write is a common task in Python 🐍.

Here is how to do it right! 🚀

👇 Image
Python has a built-in `open` that takes a file path and opens that file.

Then, you have to specify whether you want to open the file to read, write, or append.

But this isn't half of the story! Image
The default behaviour is to open the file to read/write text.

This works well with TXT or CSV files, for instance.

If you need to open a file to read its binary contents, you can add a `"b"` to the mode: Image
Read 6 tweets
May 13, 2023
The Python 🐍 built-in `round` is great. 🚀

Here are some tips on it. 👇 Image
The purpose of `round` is to… round numbers!

It rounds numbers to the closest integer.

These are some simple examples: Image
However, if the number ends in `.5`, what is the closest integer?

In that case, `round` will choose the even number.

This means it may round up or down 🤪

(In school, I was taught to round `.5` up… 🤷) Image
Read 6 tweets
May 12, 2023
Error handling in Python 🐍 made simple. 🚀

👇 Image
The keyword `try` is used before code that might fail.

So, if you know something can raise an error, you can write it inside a `try` statement: Image
Now that the code is inside a `try` statement, you need to tell Python what error you want to handle, and how.

That's when the keyword `except` comes in! Image
Read 7 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us!

:(