Rodrigo πŸπŸš€ Profile picture
Sep 8, 2021 β€’ 23 tweets β€’ 8 min read β€’ Read on X
Have you ever tried to optimise your Python 🐍 code, but weren't able to tell which alternative implementation was faster?

Let me teach you how to use the `timeit.timeit` method to measure execution time of small code snippets.

A thread πŸ‘‡πŸ§΅
If you have a snippet of code you want to time, you can use the command line to measure its execution time.

For example, how long does it take for Python 🐍 to add up the first 10,000 integers?

Using the command `python -m timeit`, I get that it takes 189 microseconds.
The execution of `python -m timeit` reports some more info:

β€œ2000 loops, best of 5: ...”

I'll explain what that means, but first let me show you how to use `timeit` in your code.

(Oh, btw, there's a MUCH better way to add consecutive integers! πŸ‘‡)
Oh, did I tell you that the `timeit` module is in the standard library? No extra installs needed.

`timeit.timeit` accepts the piece of code you want to time and returns the number of seconds it took to run.

In the REPL, summing the first 10,000 integers takes... 259 seconds⁉
That's only because the `timeit` function accepts a keyword argument `number`, that specifies the number of times your code is going to be ran.

The default value is 1,000,000 (one million) which can be quite huge!

If you want to time things that take longer, change the default!
So, when we used the CLI with `python -m timeit ...` and the program said

β€œ2000 loops, ...”

what that meant was that `number` was set to `2000`.

Let's try setting `number` to 2000 ourselves and compare the CLI result with the REPL result πŸ‘‡
The REPL clocked 0.51s for 2000 repetitions, giving 255Β΅s to run `sum(range(10_000))`.

255Β΅s is 35% times slower than 188Β΅s!

Why such a big difference?

That's why the CLI includes the β€œ..., best of 5: ...”!

Your PC has many processes running that impact performance...
To try and work around that, the CLI ran the whole experiment a total of 5 times and took the fastest time.

We take the fastest time (and not the mean) because we can interpret that value as the experiment where the other processes interfered less.

Is this making sense, so far?
So, if we want to reproduce the CLI results better, we need to run the REPL experiment 5 times and look for the best result.

Now, the fastest repetition clocked in at 0.393s for 2000 reps, giving ~197Β΅s for the snippet...

197Β΅s is much closer to 188Β΅s!
Alternatively, we can use the `timeit.repeat` method and make use of the `repeat` keyword argument.

The `repeat` method will return a list with all the times, it is then up to you to compute the `min` of that list.
I hope all of this is making sense so far.

Up to this point, you've learned how to use `timeit` from the command line, you've learned about `timeit.timeit(code, number=1000000)` and you've learned about `timeit.repeat(..., repeat=5)`.

Let's explore `timeit.timeit` a bit more.
Suppose you want to see how fast the built-in factorial function, `math.factorial`, is.

For that, let's just use `timeit.timeit` πŸ‘‡

Why do we get a `NameError`??

Because `timeit` runs the snippet in a blank environment, akin to a `.py` script that ONLY contains the snippet.
Ah, no problem, this is easy to fix.

Especially if you know that Python 🐍 can use semicolons `;` to separate statements, like I've shown you here πŸ‘‡

We just need to import the factorial before we use it and then call it...

Problem solved!
But wait!

Now the `timeit` function is _also_ timing the import statement, which is NOT what we want!

That's why the methods `timeit` and `repeat` accept a `setup` keyword argument.

`setup` specifies a piece of code that prepares the environment, e.g. with necessary imports.
Making proper usage of the `setup` keyword argument lets you measure time execution more accurately.

By removing a simple `import` statement, the time measured decreased by 24% and now represents a more accurate measurement.

Nicely done!
Now we want to compare the built-in `math.factorial` to my home-made `fact` function, using `reduce` and `operator.mul`.

First, a quick check showing that my home-made `fact` produces the correct results:
Now we can time the function, we just need to build the proper setup string...

But that's going to be a huge string!

Wouldn't it be nice if you could just tell `timeit` where to look for the `fact` name..?

You can!

That's what the `globals` argument is for πŸ‘‡
In general, you can save yourself some trouble if you use the `globals()` Python 🐍 built-in, which is a dictionary that contains all the global names that Python knows of that are _not_ built-ins.

This makes it more convenient to specify many objects:
These are just some of the cool things you can do with the standard module `timeit`!

Recap:

πŸ‘‰ use `python -m timeit <code>` to measure execution time of a simple snippet in the CL
πŸ‘‰ use `timeit.repeat` to mimic the CLI behaviour
πŸ‘‰ use `timeit.timeit` for one-off measurements
`.repeat` and `.timeit` accept some keyword arguments:

πŸ‘‰ `number`: how many times to run the code
πŸ‘‰ `setup`: set up the environment
πŸ‘‰ `globals`: dict with names needed (useful: the `globals()` built-in)
πŸ‘‰ `.repeat` also accepts `repeat` to produce more accurate measurements
I hope you enjoyed this thread, a LOT of effort was put into this!

If you did, follow me (@mathsppblog) for more content like this.

Consider sharing the beginning of the thread to share your support and appreciation for my work and for Python 🐍!

If you would like to level up your Python 🐍 game, you should definitely subscribe to my newsletter πŸ“¬, to receive gold nuggets of Python knowledge 🧠

You also get to know, first hand, when I publish my in-depth Python articles on my blog.

Subscribe:

mathspp.com/subscribe

β€’ β€’ β€’

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
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!

:(