Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Parallel Iteration With Python's zip() Function
Python’s zip()
function combines elements from multiple iterables. Calling zip()
generates an iterator that yields tuples, each containing elements from the input iterables. This function is essential for tasks like parallel iteration and dictionary creation, offering an efficient way to handle multiple sequences in Python programming.
By the end of this tutorial, you’ll understand that:
zip()
in Python aggregates elements from multiple iterables into tuples, facilitating parallel iteration.dict(zip())
creates dictionaries by pairing keys and values from two sequences.zip()
is lazy in Python, meaning it returns an iterator instead of a list.- There’s no
unzip()
function in Python, but the samezip()
function can reverse the process using the unpacking operator*
. - Alternatives to
zip()
includeitertools.zip_longest()
for handling iterables of unequal lengths.
In this tutorial, you’ll explore how to use zip()
for parallel iteration. You’ll also learn how to handle iterables of unequal lengths and discover the convenience of using zip()
with dictionaries. Whether you’re working with lists, tuples, or other data structures, understanding zip()
will enhance your coding skills and streamline your Python projects.
Free Bonus: 5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset you’ll need to take your Python skills to the next level.
Understanding the Python zip()
Function
zip()
is available in the built-in namespace. If you use dir()
to inspect __builtins__
, then you’ll see zip()
at the end of the list:
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', ..., 'zip']
You can see that 'zip'
is the last entry in the list of available objects.
According to the official documentation, Python’s zip()
function behaves as follows:
Returns an iterator of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables. The iterator stops when the shortest input iterable is exhausted. With a single iterable argument, it returns an iterator of 1-tuples. With no arguments, it returns an empty iterator. (Source)
You’ll unpack this definition throughout the rest of the tutorial. As you work through the code examples, you’ll see that Python zip operations work just like the physical zipper on a bag or pair of jeans. Interlocking pairs of teeth on both sides of the zipper are pulled together to close an opening. In fact, this visual analogy is perfect for understanding zip()
, since the function was named after physical zippers!
Using zip()
in Python
The signature of Python’s zip()
function is zip(*iterables, strict=False)
. You’ll learn more about strict
later. The function takes in iterables as arguments and returns an iterator. This iterator generates a series of tuples containing elements from each iterable. zip()
can accept any type of iterable, such as files, lists, tuples, dictionaries, sets, and so on.
Passing n
Arguments
If you use zip()
with n
arguments, then the function will return an iterator that generates tuples of length n
. To see this in action, take a look at the following code block:
>>> numbers = [1, 2, 3]
>>> letters = ["a", "b", "c"]
>>> zipped = zip(numbers, letters)
>>> zipped # Holds an iterator object
<zip object at 0x7fa4831153c8>
>>> type(zipped)
<class 'zip'>
>>> list(zipped)
[(1, 'a'), (2, 'b'), (3, 'c')]
Here, you use zip(numbers, letters)
to create an iterator that produces tuples of the form (x, y)
. In this case, the x
values are taken from numbers
and the y
values are taken from letters
. Notice how the Python zip()
function returns an iterator. To retrieve the final list object, you need to use list()
to consume the iterator.
If you’re working with sequences like lists, tuples, or strings, then your iterables are guaranteed to be evaluated from left to right. This means that the resulting list of tuples will take the form [(numbers[0], letters[0]), (numbers[1], letters[1]),..., (numbers[n], letters[n])]
. However, for other types of iterables (like sets), you might see some weird results:
>>> s1 = {2, 3, 1}
>>> s2 = {"b", "a", "c"}
>>> list(zip(s1, s2))
[(1, 'a'), (2, 'c'), (3, 'b')]
In this example, s1
and s2
are set
objects, which don’t keep their elements in any particular order. This means that the tuples returned by zip()
will have elements that are paired up randomly. If you’re going to use the Python zip()
function with unordered iterables like sets, then this is something to keep in mind.
Passing No Arguments
You can call zip()
with no arguments as well. In this case, you’ll simply get an empty iterator:
>>> zipped = zip()
>>> zipped
<zip object at 0x7f196294a488>
>>> list(zipped)
[]
Here, you call zip()
with no arguments, so your zipped
variable holds an empty iterator. If you consume the iterator with list()
, then you’ll see an empty list as well.
You could also try to force the empty iterator to yield an element directly. In this case, you’ll get a StopIteration
exception:
>>> zipped = zip()
>>> next(zipped)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
When you call next()
on zipped
, Python tries to retrieve the next item. However, since zipped
holds an empty iterator, there’s nothing to pull out, so Python raises a StopIteration
exception.
Passing One Argument
Python’s zip()
function can take just one argument as well. The result will be an iterator that yields a series of 1-item tuples:
>>> a = [1, 2, 3]
>>> zipped = zip(a)
>>> list(zipped)
[(1,), (2,), (3,)]
This may not be that useful, but it still works. Perhaps you can find some use cases for this behavior of zip()
!
As you can see, you can call the Python zip()
function with as many input iterables as you need. The length of the resulting tuples will always equal the number of iterables you pass as arguments. Here’s an example with three iterables:
>>> integers = [1, 2, 3]
>>> letters = ["a", "b", "c"]
>>> floats = [4.0, 5.0, 6.0]
>>> zipped = zip(integers, letters, floats) # Three input iterables
>>> list(zipped)
[(1, 'a', 4.0), (2, 'b', 5.0), (3, 'c', 6.0)]
Here, you call the Python zip()
function with three iterables, so the resulting tuples have three elements each.
Passing Arguments of Unequal Length
When you’re working with the Python zip()
function, it’s important to pay attention to the length of your iterables. It’s possible that the iterables you pass in as arguments aren’t the same length.
In these cases, the number of elements that zip()
puts out will be equal to the length of the shortest iterable. The remaining elements in any longer iterables will be totally ignored by zip()
, as you can see here:
>>> list(zip(range(5), range(100)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
Since 5
is the length of the first (and shortest) range()
object, zip()
outputs a list of five tuples. There are still 95 unmatched elements from the second range()
object. These are all ignored by zip()
since there are no more elements from the first range()
object to complete the pairs.
If trailing or unmatched values are important to you, then you can use itertools.zip_longest()
instead of zip()
. With this function, the missing values will be replaced with whatever you pass to the fillvalue
argument (defaults to None
). The iteration will continue until the longest iterable is exhausted:
>>> from itertools import zip_longest
>>> numbers = [1, 2, 3]
>>> letters = ["a", "b", "c"]
>>> longest = range(5)
>>> zipped = zip_longest(numbers, letters, longest, fillvalue="?")
>>> list(zipped)
[(1, 'a', 0), (2, 'b', 1), (3, 'c', 2), ('?', '?', 3), ('?', '?', 4)]
Here, you use itertools.zip_longest()
to yield five tuples with elements from letters
, numbers
, and longest
. The iteration only stops when longest
is exhausted. The missing elements from numbers
and letters
are filled with a question mark ?
, which is what you specified with fillvalue
.
Since Python 3.10, zip()
has a new optional keyword argument called strict
, which was introduced through PEP 618—Add Optional Length-Checking To zip. This argument’s main goal is to provide a safe way to handle iterables of unequal length.
The default value of strict
is False
, which ensures that zip()
remains backward compatible and has a default behavior that matches its behavior in older Python 3 versions:
>>> list(zip(range(5), range(100)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
In Python 3.10 and later, calling zip()
without altering the default value to strict
still gives you a list of five tuples, with the unmatched elements from the second range()
object ignored.
Alternatively, if you set strict
to True
, then zip()
checks if the input iterables you provided as arguments have the same length, raising a ValueError
if they don’t:
>>> list(zip(range(5), range(100), strict=True))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: zip() argument 2 is longer than argument 1
This new feature of zip()
is useful when you need to make sure that the function only accepts iterables of equal length. Setting strict
to True
makes code that expects equal-length iterables safer, ensuring that faulty changes to the caller code don’t result in silently losing data.
Looping Over Multiple Iterables
Looping over multiple iterables is one of the most common use cases for Python’s zip()
function. If you need to iterate through multiple lists, tuples, or any other sequence, then it’s likely that you’ll fall back on zip()
. This section will show you how to use zip()
to iterate through multiple iterables at the same time.
Traversing Lists in Parallel
Python’s zip()
function allows you to iterate in parallel over two or more iterables. Since zip()
generates tuples, you can unpack these in the header of a for
loop:
>>> letters = ["a", "b", "c"]
>>> numbers = [0, 1, 2]
>>> for letter, number in zip(letters, numbers):
... print(f"Letter: {letter}")
... print(f"Number: {number}")
...
Letter: a
Number: 0
Letter: b
Number: 1
Letter: c
Number: 2
Here, you iterate through the series of tuples returned by zip()
and unpack the elements into letter
and number
. When you combine zip()
, for
loops, and tuple unpacking, you can get a useful and Pythonic idiom for traversing two or more iterables at once.
You can also iterate through more than two iterables in a single for
loop. Consider the following example, which has three input iterables:
>>> letters = ["a", "b", "c"]
>>> numbers = [0, 1, 2]
>>> operators = ["*", "/", "+"]
>>> for let, num, op in zip(letters, numbers, operators):
... print(f"Letter: {let}")
... print(f"Number: {num}")
... print(f"Operator: {op}")
...
Letter: a
Number: 0
Operator: *
Letter: b
Number: 1
Operator: /
Letter: c
Number: 2
Operator: +
In this example, you use zip()
with three iterables to create and return an iterator that generates 3-item tuples. This lets you iterate through all three iterables in one go. There’s no restriction on the number of iterables you can use with Python’s zip()
function.
Note: If you want to dive deeper into Python for
loops, check out Python “for” Loops (Definite Iteration).
Traversing Dictionaries in Parallel
In Python 3.6 and beyond, dictionaries are ordered collections, meaning they keep their elements in the same order in which they were introduced. If you take advantage of this feature, then you can use the Python zip()
function to iterate through multiple dictionaries in a safe and coherent way:
>>> dict_one = {"name": "John", "last_name": "Doe", "job": "Python Consultant"}
>>> dict_two = {"name": "Jane", "last_name": "Doe", "job": "Community Manager"}
>>> for (k1, v1), (k2, v2) in zip(dict_one.items(), dict_two.items()):
... print(k1, "->", v1)
... print(k2, "->", v2)
...
name -> John
name -> Jane
last_name -> Doe
last_name -> Doe
job -> Python Consultant
job -> Community Manager
Here, you iterate through dict_one
and dict_two
in parallel. In this case, zip()
generates tuples with the items from both dictionaries. Then, you can unpack each tuple and gain access to the items of both dictionaries at the same time.
Note: If you want to dive deeper into dictionary iteration, check out How to Iterate Through a Dictionary in Python.
Notice that, in the above example, the left-to-right evaluation order is guaranteed. You can also use Python’s zip()
function to iterate through sets in parallel. However, you’ll need to consider that, unlike dictionaries in Python 3.6, sets don’t keep their elements in order. If you forget this detail, the final result of your program may not be quite what you want or expect.
Unzipping a Sequence
There’s a question that comes up frequently in forums for new Pythonistas: “If there’s a zip()
function, then why is there no unzip()
function that does the opposite?”
The reason why there’s no unzip()
function in Python is because the opposite of zip()
is… well, zip()
. Do you recall that the Python zip()
function works just like a real zipper? The examples so far have shown you how Python zips things closed. So, how do you unzip Python objects?
Say you have a list of tuples and want to separate the elements of each tuple into independent sequences. To do this, you can use zip()
along with the unpacking operator *
, like so:
>>> pairs = [(1, "a"), (2, "b"), (3, "c"), (4, "d")]
>>> numbers, letters = zip(*pairs)
>>> numbers
(1, 2, 3, 4)
>>> letters
('a', 'b', 'c', 'd')
Here, you have a list
of tuples containing some kind of mixed data. Then, you use the unpacking operator *
to unzip the data, creating two different lists (numbers
and letters
).
Sorting in Parallel
Sorting is a common operation in programming. Suppose you want to combine two lists and sort them at the same time. To do this, you can use zip()
along with .sort()
as follows:
>>> letters = ["b", "a", "d", "c"]
>>> numbers = [2, 4, 3, 1]
>>> data1 = list(zip(letters, numbers))
>>> data1
[('b', 2), ('a', 4), ('d', 3), ('c', 1)]
>>> data1.sort() # Sort by letters
>>> data1
[('a', 4), ('b', 2), ('c', 1), ('d', 3)]
>>> data2 = list(zip(numbers, letters))
>>> data2
[(2, 'b'), (4, 'a'), (3, 'd'), (1, 'c')]
>>> data2.sort() # Sort by numbers
>>> data2
[(1, 'c'), (2, 'b'), (3, 'd'), (4, 'a')]
In this example, you first combine two lists with zip()
and sort them. Notice how data1
is sorted by letters
and data2
is sorted by numbers
.
You can also use sorted()
and zip()
together to achieve a similar result:
>>> letters = ["b", "a", "d", "c"]
>>> numbers = [2, 4, 3, 1]
>>> sorted(zip(letters, numbers)) # Sort by letters
[('a', 4), ('b', 2), ('c', 1), ('d', 3)]
In this case, sorted()
runs through the iterator generated by zip()
and sorts the items by letters
, all in one go. This approach can be a little bit faster since you’ll need only two function calls: zip()
and sorted()
.
With sorted()
, you’re also writing a more general piece of code. This will allow you to sort any kind of sequence, not just lists.
Calculating in Pairs
You can use the Python zip()
function to make some quick calculations. Suppose you have the following data in a spreadsheet:
Element/Month | January | February | March |
---|---|---|---|
Total Sales | 52,000.00 | 51,000.00 | 48,000.00 |
Production Cost | 46,800.00 | 45,900.00 | 43,200.00 |
You’re going to use this data to calculate your monthly profit. zip()
can provide you with a fast way to make the calculations:
>>> total_sales = [52000.00, 51000.00, 48000.00]
>>> prod_cost = [46800.00, 45900.00, 43200.00]
>>> for sales, costs in zip(total_sales, prod_cost):
... profit = sales - costs
... print(f"Total profit: {profit}")
...
Total profit: 5200.0
Total profit: 5100.0
Total profit: 4800.0
Here, you calculate the profit for each month by subtracting costs
from sales
. Python’s zip()
function combines the right pairs of data to make the calculations. You can generalize this logic to make any kind of complex calculation with the pairs returned by zip()
.
Building Dictionaries
Python’s dictionaries are a very useful data structure. Sometimes, you might need to build a dictionary from two different but closely related sequences. A convenient way to achieve this is to use dict()
and zip()
together. For example, suppose you retrieved a person’s data from a form or a database. Now you have the following lists of data:
>>> fields = ["name", "last_name", "age", "job"]
>>> values = ["John", "Doe", "45", "Python Developer"]
With this data, you need to create a dictionary for further processing. In this case, you can use dict()
along with zip()
as follows:
>>> person = dict(zip(fields, values))
>>> person
{'name': 'John', 'last_name': 'Doe', 'age': '45', 'job': 'Python Developer'}
Here, you create a dictionary that combines the two lists. zip(fields, values)
returns an iterator that generates 2-items tuples. If you call dict()
on that iterator, then you’ll be building the dictionary you need. The elements of fields
become the dictionary’s keys, and the elements of values
represent the values in the dictionary.
You can also update an existing dictionary by combining zip()
with dict.update()
. Suppose that John changes his job and you need to update the dictionary. You can do something like the following:
>>> new_job = ["Python Consultant"]
>>> field = ["job"]
>>> person.update(zip(field, new_job))
>>> person
{'name': 'John', 'last_name': 'Doe', 'age': '45', 'job': 'Python Consultant'}
Here, dict.update()
updates the dictionary with the key-value tuple you created using Python’s zip()
function. With this technique, you can easily overwrite the value of job
.
Conclusion
In this tutorial, you’ve learned how to use Python’s zip()
function. zip()
can receive multiple iterables as input. It returns an iterator that can generate tuples with paired elements from each argument. The resulting iterator can be quite useful when you need to process multiple iterables in a single loop and perform some actions on their items at the same time.
Now you can:
- Use the
zip()
function in Python effectively - Loop over multiple iterables and perform different actions on their items in parallel
- Create and update dictionaries on the fly by zipping two input iterables together
You’ve also coded a few examples that you can use as a starting point for implementing your own solutions using Python’s zip()
function. Feel free to modify these examples as you explore zip()
in depth!
Frequently Asked Questions
Now that you have some experience with the zip()
function in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned. These frequently asked questions sum up the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
The zip()
function takes multiple iterables as arguments and returns an iterator of tuples, where each tuple contains elements from the input iterables at the same index. The iteration stops when the shortest input iterable is exhausted. If called with a single iterable, it returns an iterator of 1-tuples, and with no arguments, it returns an empty iterator.
When zip()
is used with iterables of different lengths, it stops creating tuples when the shortest iterable is exhausted. Any remaining elements in the longer iterables are ignored. However, you can use itertools.zip_longest()
to handle this situation, which will fill missing values with a specified fillvalue
.
dict(zip())
is a common pattern used to create dictionaries on the fly by zipping two iterables together. The first iterable provides the keys, and the second iterable provides the values. For example, dict(zip(["name", "age"], ("Alice", 30)))
creates the dictionary {"name": "Alice", "age": 30}
.
Yes, zip()
is lazy in Python. It returns an iterator that generates tuples only as needed, rather than creating the entire list of tuples at once. This behavior is more memory efficient, especially when dealing with large datasets.
No, there isn’t a direct unzip()
function in Python, but you can achieve the same effect by using the unpacking operator *
with zip()
. For example, zip(*zipped)
can be used to separate a list of tuples into individual sequences.
If you need to handle iterables of unequal length and want to ensure that all elements are included, you can use itertools.zip_longest()
. This function continues until the longest iterable is exhausted, filling missing values with a specified fillvalue
.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Parallel Iteration With Python's zip() Function