Python Tricks : Function Argument Unpacking

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 using the * operator:

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 it and pass its elements as separate positional arguments to the called function.

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.

猜你喜欢

转载自blog.csdn.net/weixin_41905135/article/details/124886985