The zip() function is one of the most powerful and elegant built-in functions in Python. It allows you to combine multiple iterables (lists, tuples, strings, etc.) into a single iterable object of tuples. This function is essential for any Python developer who wants to manipulate data efficiently and pythonically. If you want to master all the subtleties of this language, our complete Python course will allow you to deepen these fundamental concepts.
Definition of the zip() function
The zip() function takes one or more iterables as parameters and returns an iterator of tuples. Each tuple contains the corresponding elements from each iterable, grouped by position (index). The first tuple contains the first elements of each iterable, the second tuple contains the second elements, and so on.
The basic syntax of zip() is as follows:
zip(iterable1, iterable2, ...)This function returns a zip object, which is an iterator. This means it only generates tuples on demand, which is particularly memory-efficient when working with large amounts of data.
The zip() function stops as soon as the shortest iterable is exhausted. If you have iterables of different lengths, the extra elements from the longer iterables will be ignored.
Practical examples of zip()
Combining two lists
The most common use of zip() is to combine two list in parallel. Here is a simple example:
first_names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
# Combining the two lists
combined = zip(first_names, ages)
# Converting to list to display the result
print(list(combined))
# Result: [('Alice', 25), ('Bob', 30), ('Charlie', 35)]You can see that each first name is associated with the corresponding age according to their position in the respective lists.
Iterating over multiple lists simultaneously
One of the most frequent use cases of zip() is to iterate over multiple lists at the same time in a for loop:
products = ["Apples", "Bananas", "Oranges"]
prices = [2.50, 1.80, 3.20]
quantities = [10, 15, 8]
for product, p, q in zip(products, prices, quantities):
total = p * q
print(f"{product}: {q} units at ${p} = ${total}")
# Result:
# Apples: 10 units at $2.5 = $25.0
# Bananas: 15 units at $1.8 = $27.0
# Oranges: 8 units at $3.2 = $25.6This approach is much more elegant and readable than using indices to access elements from each list.
Creating a dictionary from two lists
The zip() function is particularly useful for creating a dict from two lists, one containing the keys and the other the values:
keys = ["name", "age", "city"]
values = ["Marie", 28, "Paris"]
# Creating the dictionary
person = dict(zip(keys, values))
print(person)
# Result: {'name': 'Marie', 'age': 28, 'city': 'Paris'}This technique is extremely practical when working with tabular data or when you need to transform data from different sources.
Handling iterables of different lengths
As mentioned earlier, zip() stops at the shortest iterable:
list1 = [1, 2, 3, 4, 5]
list2 = ['a', 'b', 'c']
result = list(zip(list1, list2))
print(result)
# Result: [(1, 'a'), (2, 'b'), (3, 'c')]
# Elements 4 and 5 are ignoredIf you need to keep all elements from iterables of different lengths, use itertools.zip_longest() which fills in missing values with a default value (usually None).
Using zip_longest for iterables of different lengths
Here is how to use zip_longest() from the itertools module:
from itertools import zip_longest
list1 = [1, 2, 3, 4, 5]
list2 = ['a', 'b', 'c']
result = list(zip_longest(list1, list2, fillvalue="N/A"))
print(result)
# Result: [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'N/A'), (5, 'N/A')]
Unzipping with zip()
An often overlooked feature is the ability to "unzip" a list of tuples using zip() with the unpacking operator *. This operation reverses the combination process:
pairs = [('Alice', 25), ('Bob', 30), ('Charlie', 35)]
# Unzipping
first_names, ages = zip(*pairs)
print(first_names) # ('Alice', 'Bob', 'Charlie')
print(ages) # (25, 30, 35)The * operator unpacks the list of tuples, then zip() groups the elements by position. The result is a tuple for each "column" of data.
Matrix transposition
This unzipping technique is particularly useful for transposing a matrix (swapping rows and columns):
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# Transposing the matrix
transposed_matrix = list(zip(*matrix))
for row in transposed_matrix:
print(row)
# Result:
# (1, 4, 7)
# (2, 5, 8)
# (3, 6, 9)This approach is much more concise than using nested loops.
Zip with generators and iterators
The zip() function works perfectly with any type of iterable, including generators created with yield:
def even_numbers(n):
for i in range(0, n, 2):
yield i
def odd_numbers(n):
for i in range(1, n, 2):
yield i
# Combining two generators
even_odd = zip(even_numbers(10), odd_numbers(10))
for even, odd in even_odd:
print(f"Even: {even}, Odd: {odd}")
# Result:
# Even: 0, Odd: 1
# Even: 2, Odd: 3
# Even: 4, Odd: 5
# Even: 6, Odd: 7
# Even: 8, Odd: 9Since zip() also returns an iterator, this combination is very memory-efficient because values are generated on demand.
Advanced use cases
Creating consecutive pairs
You can use zip() to create pairs of consecutive elements in a list:
data = [10, 20, 30, 40, 50]
# Creating consecutive pairs
consecutive_pairs = list(zip(data, data[1:]))
print(consecutive_pairs)
# Result: [(10, 20), (20, 30), (30, 40), (40, 50)]This technique is very useful for calculating differences between consecutive elements or for analyzing time series.
Sliding window grouping
You can extend this idea to create sliding windows of any size:
data = [1, 2, 3, 4, 5, 6, 7]
# Windows of size 3
windows = list(zip(data, data[1:], data[2:]))
for window in windows:
print(f"Window: {window}, Average: {sum(window)/3:.2f}")
# Result:
# Window: (1, 2, 3), Average: 2.00
# Window: (2, 3, 4), Average: 3.00
# Window: (3, 4, 5), Average: 4.00
# Window: (4, 5, 6), Average: 5.00
# Window: (5, 6, 7), Average: 6.00
Numbering elements with enumerate and zip
You can combine zip() with enumerate() to get both the index and the combined elements:
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
for index, (name, score) in enumerate(zip(names, scores), start=1):
print(f"{index}. {name}: {score} points")
# Result:
# 1. Alice: 95 points
# 2. Bob: 87 points
# 3. Charlie: 92 points
Best practices with zip()
Explicitly convert to list if necessary
Don't forget that zip() returns an iterator, not a list. If you need to access elements multiple times or use list operations, convert it explicitly:
# Bad practice - the iterator is exhausted after first use
result = zip([1, 2], ['a', 'b'])
print(list(result)) # [(1, 'a'), (2, 'b')]
print(list(result)) # [] - empty list because iterator is exhausted
# Good practice - convert immediately if necessary
result = list(zip([1, 2], ['a', 'b']))
print(result) # [(1, 'a'), (2, 'b')]
print(result) # [(1, 'a'), (2, 'b')] - still works
Check iterable lengths
In Python 3.10+, you can use the strict=True parameter to raise an error if iterables don't have the same length:
# Python 3.10+
list1 = [1, 2, 3]
list2 = ['a', 'b']
try:
result = list(zip(list1, list2, strict=True))
except ValueError as e:
print(f"Error: {e}")
# Error: zip() argument 2 is shorter than argument 1
Prefer zip() over indices
Always use zip() rather than indices when iterating over multiple lists in parallel. It's more readable and more pythonic:
# Avoid
names = ["Alice", "Bob"]
ages = [25, 30]
for i in range(len(names)):
print(f"{names[i]} is {ages[i]} years old")
# Prefer
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
Summary table
| Operation | Code | Result |
|---|---|---|
| Combine two lists | zip([1,2], ['a','b']) | [(1,'a'), (2,'b')] |
| Unzip | zip(*pairs) | Separate tuples |
| Create a dictionary | dict(zip(keys, values)) | Dictionary |
| Different lengths | zip_longest() | Completes with fillvalue |
| Strict check | zip(..., strict=True) | ValueError if lengths ≠ |
Frequently asked questions
What is the difference between zip() and itertools.zip_longest()?
The zip() function stops as soon as the shortest iterable is exhausted, ignoring the remaining elements from longer iterables. In contrast, itertools.zip_longest() continues until the longest iterable is exhausted, filling in missing values with a default value (by default None, but you can specify another value with the fillvalue parameter).
Why does zip() return an iterator instead of a list?
The zip() function returns an iterator for memory efficiency reasons. With an iterator, tuples are generated on demand (lazy evaluation), which is particularly important when working with large datasets. If you need a list, you can simply use list(zip(...)).
Can you use zip() with more than two iterables?
Absolutely! The zip() function accepts an unlimited number of iterables. For example, zip(list1, list2, list3, list4) will produce tuples of 4 elements, each coming from the corresponding iterable. This is very useful when you need to process data from multiple sources in parallel.
How can I learn to master zip() and other Python functions?
To master zip() and all of Python's built-in functions, regular practice is essential. We recommend following our Python course on Believemy, which covers in depth all the essential functions of the language, with practical exercises and concrete projects to consolidate your knowledge.