Aside: Argument Packing and Unpacking

Argument (un)packing is a peculiar part of Python’s syntax, and unfortunately is not documented particularly well. If you’ve ever seen a function definition like:

def foo(bar, baz, *args, **kwargs):
    ...

or a function call like:

do_something(*packed_args)

these are examples of argument packing and unpacking.

Packing

Packing and unpacking are essentially two separate techniques. Argument packing occurs in a function definition, and allows the function to take an arbitrary number of arguments. Extra positional arguments (beyond those explicitly included in the function definition) are stored in a tuple (often called *args by convention, but the variable name is irrelevant, only the * syntax matters).

Example:

def print_args(first, second, *args):
    print "Positional args:", first, second
    for arg in args:
        print "Unpacked arg:" + str(arg)

If we then call print_args() with more than two arguments:

print_args(42, "spam", "eggs")

# Output:
#    Positional args: 42 spam
#    Unpacked arg: eggs

(Note, however, that if the definition of print_args did not include a * argument, this call would raise a TypeError.)

Extra keyword arguments are stored in the ** argument (conventionally called **kwargs, though again only the ** syntax matters.) These work much the same as positional arguments: any extra keyword arguments are packed into a dictionary object and passed to the function as kwargs.

Example:

def print_kwargs(arg, **kwargs):
    print arg
    for k in kwargs:
        print "Keyword:", k, "Value:", kwargs[k]

And an example call:

print_kwargs(1, a=1, b=4, c="foo")
# Output:
#    1
#    Keyword: a Value: 1
#    Keyword: c Value: foo
#    Keyword: b Value: 4

As one example of a real use case, both of these are necessary in defining decorators (which must wrap a function and cannot tell beforehand how many arguments will be given).

Unpacking

Argument unpacking is essentially the same process in reverse. Consider the following code:

def find_distance(x1, y1, x2, y2):
    dx = abs(x2 - x1)
    dy = abs(y2 - y1)
    return math.sqrt(dx**2 + dy**2)

point_a = (5, 4)
point_b = (8, 7)

distance = find_distance(point_a[0], point_a[1], point_b[0], point_b[1])

Sure, indexing the tuple *works*, but it’s both ugly and potentially problematic to maintain. (Oh, suddenly we need to work with points in three dimensions?) Probably the best way to handle the problem would be to rewrite the find_distance function to expect tuple arguments, but what if it’s library code?

Instead, we can use the unpacking syntax:

distance = find_distance(*point_a, *point_b)

This unpacks each tuple to its component parts, and therefore produces the same result as before, but with a much cleaner line of code.

Note that you can also unpack a *slice* of a tuple, i.e.:

point_c = (2, 2, 2)
distance_b = find_distance(*point_a, *point_c[:2])

Once again, the same applies to keyword arguments:

def set_flags(obj, flag_a=False, flag_b=False, flag_c=False):
    obj.flag_a = flag_a
    obj.flag_b = flag_b
    obj.flag_c = flag_c

flags = {
    "flag_a": True,
    "flag_b": True,
    "flag_c": True
}

foo.set_flags(**flags)

The ** unpacks the flags dict and submits the key/value pairs as keyword arguments.

These are powerful tools. As always, take care to use argument unpacking only where necessary or where it makes code cleaner and easier to understand.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s