Python Tricks: Function Argument Unpacking
A really cool but slightly arcane feature is the ability to “unpack” funciton arguments from sequences and dictionaries with the * and ** operators.
Let’s define a simple funciton to work with as an example:
In [3]: def print_vector(x, y, z):
...: print('<%s, %s, %s>' %(x, y, z))
As you can see, this function takes three arguments(x, y, and z) and prints them in a nicely formatted way. We might use this function to pretty-print 3-dimentional vectors in our program:
In [4]: print_vector(0, 1, 0)
<0, 1, 0>
Now depending on which data structure we choose to represent 3D vectors with, printing them with our print_vector function might feel a little awkward. For example, if our vectors are represented as tuples or lists we must explicitly specify the index for each component when printing them:
In [7]: print_vector(tuple_vec[0],
...: tuple_vec[1],
...: tuple_vec[2])
<1, 0, 1>
Using a normal function call with separate arguments seems unnecessarily verbose and cumbersome. Wouldn’t it be much nicer if we could just “explode” a vector object into its three components and pass everything to the print_vector function all at once?
(Of course, you could simply redefine print_vector so that it takes a single parameter representing a vector object–but for the sake of having a simple example, we’ll ignore that option for now.)
Thankfully, there’s a better way to handle this situation in Python with Function Argument Unpacking
In [8]: print_vector(*tuple_vec)
<1, 0, 1>
In [9]: print_vector(*list_vec)
<1, 0, 1>
Putting a * before an iterable in a function call will unpack
This technique works for any iterable, including generator expressions. Using the * operator on a generator consumes all elements from the generator and passes them to the function:
In [10]: genexpr = (x * x for x in range(3))
In [11]: print_vector(*genexpr)
<0, 1, 4>
Besides the * operator for unpacking sequences like tuples, lists, and generators into positional arguments, there’s also the ** operator for unpacking keyword arguments from dictionaries. Imagine our vector was represented as the following dict object:
In [12]: dict_vec = {'y': 0, 'z': 1, 'x':1}
We could pass this dict to print_vector in much the same way using the ** operator for unpacking:
In [13]: print_vector(**dict_vec)
<1, 0, 1>
Because dictionaries are unordered, this matches up dictionary values and function arguments based on the dictionary keys: the x argument receives the value associated with the ‘x’ key in the dictionary.
If you were to use the single asterisk(*) operator to unpack the dictionary, keys would be passed to the function in random order instead:
In [14]: print_vector(*dict_vec)
<y, z, x>
Python’s function argument unpacking feature gives you a lot of flexibility for free. Often this means you won’t have to implement a class for a data type needed by your program. As a result, using simple built-in data structures like tuples or lists will suffice and help reduce the complexity of your code.