The Magic of Asterisks in Python

Packing and Unpacking

Syahrul Hamdani
Analytics Vidhya

--

Python right now is the most popular programming language based on PYPL. This below graph tells all.

Programming language popularity based on PYPL per March 2020.

Now, how does python can be so much popular nowadays? It’s because of magic! Yes, python’s magic offers a handful of features for developers. Asterisk (*) is one of the magic.

1. Magic 1: Packing

We often use print to display objects to the stream file output. If you look at the documentation of the print function, the parameter *objects is a packing argument. Say, you want to print a variable text = "Work From Home" and add a prefix sentence: . You can just write:

>>> text = "Work From Home"
>>> print("sentence:", text)
sentence: Work From Home

This is because your arguments passed into print are all packed in variable objects. The asterisk (*) prefix in the variable objectis used to tell python that it’s a packing argument, “Dear python, accept all arguments!”. You do this the same way as you pass multiple arguments. So, you actually can pass any number of arguments into the function.

>>> print("this", "could", "work", "too.", "But,", "a", "waste", "of", "time")

How could this be possible?

Now, let’s create our own function to demystify this.

This function will take any number of arguments you pass. We print the whole objects, we will see what type it is, and we will do a looping (wait, what?). If you pass these long arguments into the function,

demystify_single_asterisk(a, text, ["text one", "text two"], {"your", "set"}, 100, .30, {"this": "is", "your": "dict"}, ("tuple", "too"))

We will have an output below.

Very cool magic!

It turns out that Python treats our packing arguments as a tuple. So, we can perform any operation just like any other tuple, such as indexing or looping.

This single asterisk (*) gives us cool magic. How about we add another asterisk to be a double asterisk (**)?

If you ever use Matplotlib plot function, you may have encountered a parameter called **kwargs. This is also a packing argument, as a complement to packing argument *args. You may have tried to pass a keyword argument label into the function, but you can’t find it in the parameters list. This is handled by double asterisks packing argument. Let’s create another function similar to the previous one to demystify this.

Keyword argument is an argument preceded by an identifier (e.g. name=)

Now, try to call this function with the below arguments.

demystify_double_asterisk(name="Tony", job="Hero", quotes="I love you tons")

We will get output like this.

So, by preceding a parameter with double asterisks (**) will treat it as a packing argument that yields a Python dictionary. The keyword name will act as a dictionary key and the argument will act as a dictionary value. Hence, as we pass name='Tony', Python will treat it like a dictionary {'name': 'Tony'}. The full dictionary of our arguments above is like below.

{'name': 'Tony', 'job': 'Hero', 'quotes': 'I love you tons'}

An example of how useful this magic is by creating our own function, say to make an HTML tag (heavily inspired by Trey Hunner).

def make_tag(tag_name, **attr):
attributes = [f"{name}={value}" for name, value in attr.items()]
return f"<{tag_name} {' '.join(attributes)}>"

If you want to make an image tag, you can just type below syntax.

make_tag("img", width=100, height=100, href="'img/example.png'")

2. Magic 2: Unpacking

The opposite magic of packing argument is, of course in a grammatical manner, unpacking. Unpacking will well, unpack, all python iterable. Let’s use the print function again. If you define a list my_list like below and then print it, you will get

>>> my_list = ["write", "from", "home"]
>>> print(my_list)
['write', 'from', 'home']

Now, if you prepend an asterisk (*) before my_list inside the print function, you will get

>>> print(*my_list)
write from home

Magic!

The asterisk (*) beforemy_list will unpack items in my_list into separated arguments passed into the print function, hence it will behave as we pass arguments like print('write', 'from', 'home'). Let’s dive in!

Here, I created a simple function demystify_unpack that accepts any number of arguments packed in iterables. This function just prints the arguments and the data type.

def demystify_unpacking(*iterables):
print(iterables, type(iterables)

We then pass my_list and *my_list as an argument. The output of these two experiments, respectively, are:

>>> (['write', 'from', 'home'],) <class 'tuple'>
>>> ('write', 'from', 'home') <class 'tuple'>

As we know, any number of arguments will be packed by iterables hence the type is always a tuple. The difference is my_list passing a list, but *my_list will unpack it first and pass multiple arguments just like we pass 'write', 'from', 'home' into the function.

Another utility using this magic when it’s used to unpack iterable in an assignment (first introduced in PEP 3132). Here are some examples.

>>> seq = ["write", "and", "work", "from", "home", "is", "awesome"]
>>> a, b, c, *d = seq
>>> print(a)
write
>>> print(b)
and
>>> print(c)
work
>>> print(d)
['from', 'home', 'is', 'awesome']

The left side of the operator will be assigned items from seq with a is the first item, b is the second, and c get the third item. For the variable d, we will get all remaining items thanks to the magic of unpacking. You may want to try another assignment such as *k, l, m = seq which results k equals to ['write', 'and', 'work', 'from', 'home'].

That’s It!

We only cover packing and unpacking in this article, but there are other magics which still exist such as asterisk in a list or dict literals and the usage in keyword-only arguments and positional arguments with keyword-only arguments. We will cover them in the next article.

References and Resources

[1] Trey Hunner, Asterisks in Python: what they are and how to use them (2018)

[2] PEP 3132 (2007)

--

--