Understand zip() — A Hidden Gem in Python
By: Xiaoxu Gao
Tags: Programming, Software Development, Python, Data Science, Towards Data Science

zip() built-in function

Python has many hidden gems and zip() is one of them. It’s able to solve the same problem in a cleaner way. We all like clean code, don’t we?

From the official Python documentation, zip(*iterables) makes an iterator that aggregates elements from each of the iterators.

Recap on unpacking operator(*)

The single asterisk (*) means it unpacks the iterators. For example, if you have two lists list1=["a1","a2"] and list2=["b1","b2"] , you don’t have to create a new list of arguments and pass it to the function like zip([list1,list2]) , but instead, you can just do zip(list1, list2) .

Unpacking operator (*) can be used when you don’t know the number of arguments of your function. For example, you can calculate the sum of an unknown number of arguments.

Another unpacking operator is (**). The two asterisks unpack dictionaries. It means that each argument must have a key, that’s why you normally see **kwargs (keyword arguments) as the input name. Just to be clear, you can give any name to it like **nums .

If you want to know more about *args and **kwargs , I would recommend you to read Python args and kwargs: Demystified from Real Python.

zip() vs. zip_longest()

Let’s talk about zip() again. We’ve understood that the input of zip(*iterables) is a number of iterators. The iterator can be a str, list, tuple, set, or dictionary. Internally, zip() loops over all the iterators multiple rounds. In each round, it calls next() function to each iterator and puts the value in a tuple and yield the tuple at the end of the round. Then it continues with the next round. The loop will be over if any of the iterators is exhausted. Maybe it’s easier to read the code. Python documentation gives the following code to help readers understand how zip() works under the hood.

Then let’s solve the previous problem with zip() . It yields a tuple each time. An advantage of using yield is to save RAM usage because we don’t need to store the entire result sequence in memory.

The default zip() follows Cannikin Law which means the length of the merged result is dependent on the shortest input iterable. That’s why in the previous example, you don’t see country X in the output.

Cannikin Law (source: blog)

However, if you want the merged result to align with the longest input iterator, you can use itertools.zip_longest . In that case, the missing value will be filled with fillvalue defined by the user.

unzip

Since we can “zip” 2 iterators, we must be able to “unzip” it as well. The logic is unzipped = zip(*zip(*iterables)) . In the example code, both zipped and unzipped objects have class zip , and they are not readable. We can convert them to a list or a tuple.

Edge cases

There is no constraint on the number of input iterators. It can be 0, 1, or more. The cases where you have 0 or 1 input iterator are not very common, but it’s still possible to do that.

Dictionary in zip()

What would be the result of the following code? Think for a moment before reading further.

The answer is ("city", "city", "city") and (“country”, “country”, “country”) . Why do we only get keys?

According to what we’ve seen previously, internally zip() performs iter() to each input argument. So the question is what’s the output of iter(nl) ? If we execute print(iter(nl)) , we will get <dict_keyiterator object at 0x10e1e3f50> . That’s why we only receive keys in the result.

A better way is to use nl.items() as the input. In that case, we are able to receive both key and value in the loop.

Mixed type in zip()

There is no constraint on the type of iterators either. In the following code, we use mixed types as input arguments. What would be the result? Think for a moment. This example is just for an educational purpose. It shouldn’t be the best code you’ve ever seen. 🙂

Alright, the answer is:

('w', 'Amsterdam', ('key1', 'Netherlands'))

('o', 'Berlin', ('key2', 'Germany'))

('r', 'Chongqing', ('key3', 'China'))

Each ele is a tuple of 3 elements that coming from 3 input arguments. The length of string should be considered as 5, not 1. Each loop will return 1 character. In total, the program loops 3 times because the shortest length of inputs is 3.

If you are able to answer this question and explain the reasons behind it, then you’ve mastered this chapter.