Args vs kwargs: which is the fastest way to call a function in Python?
A clear demonstration of the timeit module
Have you ever wondered if it’s slower to call a function with keywords than without? In other words: which is faster; positional arguments (myfunc('mike', 33)
) or kwargs (myfunc(name='mike', age=33)
)?
In this short and simple article we find out if it is worth sacrificing the readability of passing keywords arguments vs passing arguments positionally. We set up a benchmark with the timeit
module and compare the results. Nothing too complicated; let’s code!
The function
To benchmark the performance of function calls we first need a function that we can call:
def the_func(arg1, arg2):
pass
The function contains nothing more than a pass
meaning that the function in itself does nothing. This ensures that we can single out and compare the way we call the function (i.e. positionally or with kwargs).
The benchmarking script
Next we’ll need some code that calls the function a number of times and records the time it takes for the function to execute:
import timeit
number = 25_000_000
repeat = 10
times_pos: [float] = timeit.repeat(stmt="func('hello', 'world')", globals={'func': the_func}, number=number, repeat=repeat)
times_kwarg: [float] = timeit.repeat(stmt="func(arg1='hello', arg2='world')", globals={'func': the_func}, number=number, repeat=repeat)
This is where the timeit
module comes in. We use timeit.repeat
to time running a function a number
of times. We define the function call in stmt
and map the func
in the stmt to the function we’ve defined earlier using the globals
dict. Then we repeat the experiment a number of times using the repeat
argument. We end up with an array of 10 floats: each representing running the function call 25 million times.
Checking out the results
With the code below we take the list of floats that timeit
provides us with and display the min, max and average execution time.
print("\t\t\t min (s) \t max (s) \t avg (s)")
print(f"pos: \t\t {min(times_pos):.5f} \t {max(times_pos):.5f} \t {sum(times_pos) / len(times_pos):.5f}")
print(f"arg only: \t {min(times_arg_only):.5f} \t {max(times_arg_only):.5f} \t {sum(times_arg_only) / len(times_arg_only):.5f}")
Done! Let’s do some benchmarking!
The results
The results are in:
min (s) max (s) avg (s)
pos: 1.38941 1.72278 1.58808
kwarg: 1.72834 1.76344 1.75132
min (s) max (s) avg (s)
pos: 2.07883 2.77485 2.35694
kwarg: 2.05186 3.05402 2.74669
It seems that, on average, positional arguments are faster by 0.39 seconds or a little over 16.5%. Realise, however, that positional arguments are 0.39 seconds faster in calling the function 25 million times.
This means that choosing positional arguments over keyword arguments saves you about 16 nano-seconds; the time it takes for light to cross a distance of about 4.8 meters (16 feet).
Conclusion
Let’s start with the main conclusion: don’t stop using kwargs over performance concerns! Personally I like to use keyword arguments because it keeps my code readable and reduces the change of mixing up arguments. In this article we’ve seen that using keyword arguments come with a negligible performance increase. Go for readability for the price of a few nano seconds speed penalty.
I hope this article was as clear as I hope it to be but if this is not the case please let me know what I can do to clarify further. In the meantime, check out my other articles on all kinds of programming-related topics like these:
- Git for absolute beginners: understanding Git with the help of a video game
- Create and publish your own Python package
- Create a fast auto-documented, maintainable, and easy-to-use Python API in 5 lines of code with FastAPI
Happy coding!
— Mike
P.S: like what I’m doing? Follow me!