How to use the zip() function to zip and unzip iterable objects in Python

Introduction
Let’s say that we have two lists, one that includes first names, and the other includes last names. We would like to somehow combine the first names with the corresponding last names as tuples. In other words, we would like to combine elements from multiple iterables that have the same index together in a list of tuples:
list_1 = ['Jane', 'John', 'Jennifer']
list_2 = ['Doe', 'Williams', 'Smith']
Desired Output:
[('Jane', 'Doe'), ('John', 'Williams'), ('Jennifer', 'Smith')]
zip() function
We can accomplish this with the zip() function, which is a built-in python function. The zip() function is named due to its analogous mechanism as physical zippers. When you zip something, you bring both sides together. And that’s how the zip() function works! It brings elements of the same index from multiple iterable objects together as elements of the same tuples.
zip(*iterables)
The zip() function takes in iterables as arguments, such as lists, files, tuples, sets, etc. The zip() function will then create an iterator that aggregates elements from each of the iterables passed in. In other words, it will return an iterator of tuples, where the i-th tuple will contain the i-th element from each of the iterables passed in. This iterator will stop once the shortest input iterable has been exhausted.
Using the zip() function
Well, based on the above goal, we have two lists (which are iterable objects), and we would like to combine the same indexed elements from each of these lists together. Thus, we can use the zip() function to accomplish this as follows:
first_names = ['Jane', 'John', 'Jennifer']
last_names = ['Doe', 'Williams', 'Smith']
full_names = list(zip(first_names, last_names))
print(full_names)
# [('Jane', 'Doe'), ('John', 'Williams'), ('Jennifer', 'Smith')]
Remember, the zip() function returns an iterator. Thus, we need to use the list() function that will use this returned iterator (or zip object) to create a list. In addition, as long as the iterables passed in are ordered (sequences), then the tuples will contain elements in the same left-to-right order of the arguments passed in the zip() function.
What if we have three iterable objects?
Let’s say we have another list, ages, that contains the age of the corresponding individual in the other two lists, _firstnames and _lastnames. We would like to also include the ages in in the tuple with the first and last name. Well, as mentioned above, the zip() function takes in any number of iterables.
first_names = ['Jane', 'John', 'Jennifer']
last_names = ['Doe', 'Williams', 'Smith']
ages = [20, 40, 30]
names_and_ages = list(zip(first_names, last_names, ages))
print(names_and_ages)
# [('Jane', 'Doe', 20), ('John', 'Williams', 40), ('Jennifer', 'Smith', 30)]
Notice how the names_and_ages contains tuples with n elements (the same number of arguments, or iterable objects, we passed in to the zip() function).
Passing in one argument to zip()
If we only pass in one iterable object to the zip() function, then we will get a list of 1-item tuples as follows:
first_names = ['Jane', 'John', 'Jennifer']
print(list(zip(first_names)))
# [('Jane',), ('John',), ('Jennifer',)]
Iterables with unequal lengths
What if we pass in lists (or other iterable objects) of unequal lengths? In other words, let’s say that our _lastnames list contains 1 more element than _firstnames. Well, as mentioned above, the iterator returned by the zip() function will stop once the shortest input iterable has been exhausted. In other words, our list of tuples will only contain the elements from the indexes that are present in all the iterables passed in to the zip() function. Thus, the remaining elements in the longer iterables will be ignored.
first_names = ['Jane', 'John', 'Jennifer']
last_names = ['Doe', 'Williams', 'Smith', 'Jones']
full_names = list(zip(first_names, last_names))
print(full_names)
# [('Jane', 'Doe'), ('John', 'Williams'), ('Jennifer', 'Smith')]
_If the elements in the longer iterables are needed, then we can use the itertools.zip_longest() (ziplongest() function located in the itertools module) function instead of zip(). It will continue until the longest iterable is exhausted, and will replace any missing values with the value passed in for the fillvalue argument (default is None).
Parallel Iteration of Iterables
We can use the zip() function to iterate in parallel over multiple iterables. Since the zip() function returns an iterator, we can use this zip object (the iterator it returns) in a for loop. And since with each iteration of this iterator a tuple is returned, we can unpack the elements of this tuple within the for loop:
first_names = ['Jane', 'John', 'Jennifer']
last_names = ['Doe', 'Williams', 'Smith']
for first, last in zip(first_names, last_names):
print(first, last)
# Output:
Jane Doe
John Williams
Jennifer Smith
Or we can have three iterables:
first_names = ['Jane', 'John', 'Jennifer']
last_names = ['Doe', 'Williams', 'Smith']
ages = [20, 40, 30]
for first, last, age in zip(first_names, last_names, ages):
print(f'{first} {last} is {age} years old')
# Output:
Jane Doe is 20 years old
John Williams is 40 years old
Jennifer Smith is 30 years old
Another example of parallel iteration:
We have two lists: a list of revenues and a list of costs. We would like to make a new list, profits, that is the difference between the revenues and costs. We can accomplish this using parallel iteration:
revenue = [30000, 50000, 70000, 90000]
cost = [10000, 15000, 20000, 30000]
profit = []
total_profit = 0
for revenue, cost in zip(revenue, cost):
profit.append(revenue - cost)
total_profit += revenue - cost
print(profit)
# [20000, 35000, 50000, 60000]
print(total_profit)
# 165000
Unzipping in python
Let’s say that we have the following list of tuples:
first_and_last_names = [('Jane', 'Doe'), ('John', 'Williams'), ('Jennifer', 'Smith')]
And we want to separate the elements in these tuples into two separate lists. Well, since that is the opposite of zipping (bringing things together), it would be unzipping (taking things apart). To unzip in python, we can use the unpacking operator * with the zip() function as follows:
first_names, last_names = zip(*first_and_last_names)
first_names = list(first_names)
last_names = list(last_names)
print(first_names)
# ['Jane', 'John', 'Jennifer']
print(last_names)
# ['Doe', 'Williams', 'Smith']
The unpacking operator * will unpack the first_and_last_names _list of tuples into its tuples. These tuples will then be passed to the zip() function, which will take these separate iterable objects (the tuples), and combines their same-indexed elements together into tuples, making two separate tuples. Lastly, through tuple unpacking, these separated tuples will be assigned to the first_names and lastnames variables. We then use the list() function to convert these tuples into lists.
*For more on unpacking operators ( and ), iterables, iterators, and iteration, check out the following two blogs:
If you enjoy reading stories like these and want to support me as a writer, consider signing up to become a Medium member. It’s $5 a month, giving you unlimited access to stories on Medium. If you sign up using my link, I’ll earn a small commission.
Conclusion
In this tutorial, we looked at how the zip() function works in python. We learned how the zip() function operates in different scenarios, such as with one iterable, or with iterables that have unequal lengths. We then saw how we can use the zip() function to iterate over multiple iterable objects in parallel. And lastly, we learned how to use the unpacking operator * to unzip objects in python.