A Guide to Args, Kwargs, Packing and Unpacking in Python | by Edward Krueger | Towards Data Science

By: Edward Krueger and Douglas Franklin.
We’ve all heard of arguments and keyword arguments (args and kwargs) when discussing Python functions. Arguments usually consist of numerical values, while keyword arguments, as the name suggests, are semantic. When writing functions, *args and **kwargs are often passed directly into a function definition.
This function can handle any number of args and kwargs because of the asterisk(s) used in the function definition. These asterisks are packing and unpacking operators.
When this file is run, the following output is generated.
Notice that the arguments on line 5, two args and one kwarg, get correctly placed into the print statement based on their type. Also of note are the parenthesis and curly brackets in the output. Args have been packed into a tuple and kwargs a dictionary.
Before we get more into packing and unpacking values in Python, let's talk a little more about function arguments.
There are 2 kinds of arguments in Python, positional arguments and keyword arguments, the former are specified according to their position and the latter are the arguments are key-value pairs.
Arguments without defaults cannot be omitted when calling a function. They must be passed in the correct order and position.
Let's talk a little about packing and unpacking.
The asterisks are unpacking operators that unpack the values from iterable objects in Python. The single asterisk operator (*) commonly associated with args can be used on any iterable. The double asterisk (**) associated with kwargs can only be used on dictionaries.
These asterisks are an operator for packing and unpacking. However, you can pack and unpack without them. Check out the code below.
In this gist, we have the tuple, t, being unpacked into three variables; a, b and c. When we print these vars, we will see the individual elements of the tuple.
123
Let’s use this same unpacking pattern on a dictionary.
When k and v are printed, we see:
Hello15
This example is a little more complicated, as you can see on line 3. We use .items() to iterate over a dictionary, tuple() to convert the dictionary to a tuple so that we can use [0] to pull the first element of the tuple. All of this packs the key-value pair into k and v .
Now that you know a little about unpacking values in Python, let's get into the operators * and **. Take a look at this example of both packing and unpacking with the single asterisk.
We use the same pattern of unpacking by setting three variables equal to a list. However, here we add the packing operator to the variable b. Can you guess what the print statements will look like?
1[2,3,4,5]6
We can see that a was set equal to the first element in the list, c to the last element and all the elements in between were packed into b . This could be useful when you know how many variables you want to define but are unsure how many elements you are placing into each.
The double asterisk allows us to make some otherwise complex dictionary processes occur quite elegantly. For example, we can use the double-asterisk to merge two uniquely keyed dictionaries.
When we run this file, we see the following output.
Notice that the return statement in merge_two_dictionaries is wrapped in curly brackets. This ensures our function returns a dictionary.
Now let's move to a discussion about *args and **kwargs .
Note that args is just a name. You’re not required to use the name args. All that matters here is that you use the unpacking operator (*). It is, however, canonical to use args. Bear in mind that the iterable object you’ll get using the unpacking operator * is not a list but a tuple.
When you use the * operator to unpack a list and pass arguments to a function, it’s exactly as though you’re passing every single argument alone. Let’s show the power of *args with a common coding interview question.
You are tasked with writing a Python function that will sum integers.
Sounds easy enough, right?
My initial code might look something like this.
Fits the bill of ‘sums integers’ for sure. Let’s say we run it against these tests. Can you see what will go wrong?
Our first two tests work no problem, but the third, summing three integers, generates an error. It looks like our sum function can only take two positional arguments.
Let’s see if we can code a better addition function using *args.
Now we have a function that can take in an arbitrary number of arguments. The sum is calculated by looping through these arguments and adding their value to total.
Now let's run it past some tests.
This new function passes all of them! When we want to take in many arguments, but we are not sure how many, *args is a great option.
Once again, you’re not required to use the name kwargs. All that matters here is that you use the unpacking operator (**). It is, however, canonical to use the name kwargs.
In function definitions **kwargs works like *args, but instead of accepting positional arguments, it accepts arbitrarily many keyword arguments.
**kwargs is often used to preserve a message as it is passed between objects. We can see this is the decorator code below. If you would like to learn more about decorators, check out the article this code is from.
towardsdatascience.com
On line 8, we see that tracefunc_closure takes **kwargs as an argument. This allows whatever we pass to tracefunc to be preserved as it is passed between objects. Let's trace a function with this decorator to see this firsthand.
Here is the print statement output.
Notice that tracefunc recognizes the dataframes as args and the keyword arguments for pd.merge as kwargs. Additionally, we didn't have to do anything to tracefunc to make it compatible with pd.merge. Tracefunc is able to take in these kwargs and pass them to pd.merge. This represents interface preservation.
We can see examples of both keyword arguments and **kwargs in the pandas documentation. Here we see that dataframe.info can take 6 different keyword arguments. In cases like this, we must use the proper keywords or place the values in thier positional order. This later case however unnecessarily sacrifices readiblilty.
The dataframe.assign takes**kwargs as its only argument.
That sounds simple, however **kwargs is very flexible. When passing key-value pairs as **kwargs to df.assign our values can be a simple list or a more complex lambda function. The list and lambda-function processing is implemented in the source code for .assign. The significance of **kwargs is that this function can take any key and use it. It doesn't matter if we name the key state or temp_f .
Whether writing functions to take an arbitrary number of arguments and keyword arguments, or to pack and unpack values from lists and dictionaries *args and **kwargs allow for some remarkably flexible, readable and useful code. Additionally, *args and **kwargs are fundamental to understanding broader programming and function writing concepts in Python.