Sign in to change your settings
Sign in to your Python Morsels account to save your screencast settings.
Don't have an account yet? Sign up here.
What happens if you use Python's built-in zip function with iterables that have different lengths?
Zipping iterables matches up each iterable item that's at the same position
Python's zip function is helpful for looping over multiple iterables at the same time:
>>> numbers = [2, 1, 3, 4, 7]
>>> letters = "abcde"
>>> for number, letter in zip(numbers, letters):
... print(f"{number} is {letter}")
...
2 is a
1 is b
3 is c
4 is d
7 is e
The zip function isn't related to .zip files.
It's related to a zipper on a piece of clothing.
Similar to a zipper, zipping two iterables together will match up each of the iterable items that are at the same position:
>>> list(zip(numbers, letters))
[(2, 'a'), (1, 'b'), (3, 'c'), (4, 'd'), (7, 'e')]
Though, unlike with a physical zipper, the zip function also works with any number of iterables.
Here we're zipping together three different iterables:
>>> numbers = [2, 1, 3, 4, 7]
>>> letters = "abcde"
>>> names = ["Luciano", "Jane", "Sandra", "Iva", "Diana"]
>>> list(zip(numbers, letters, names))
[(2, 'a', 'Luciano'), (1, 'b', 'Jane'), (3, 'c', 'Sandra'), (4, 'd', 'Iva'), (7, 'e'
, 'Diana')]
The zip function works with any iterables
Python's zip function works with any iterable, not just sequences.
For example, we could zip together a list and a generator object:
>>> numbers = [2, 1, 3, 4, 7]
>>> squares = (n**2 for n in numbers)
>>> list(zip(numbers, squares))
[(2, 4), (1, 1), (3, 9), (4, 16), (7, 49)]
Or we could zip together each of the lines in two different files:
>>> with open("file1.txt") as file1, open("file2.txt") as file2:
... for line1, line2 in zip(file1, file2):
... print(line1.strip(), line2.strip())
...
The light of a candle In the twilight rain
is transferred to another candle- These brilliant-hued hibiscus -
spring twilight. A lovely sunset.
But what if the iterables don't have the same number of items?
What happens with different length iterables?
If we zip together iterables with different numbers of items, what might Python do?
>>> lucas = [2, 1, 3, 4, 7, 11, 18]
>>> fib = [1, 1, 2, 3, 5]
>>> list(zip(lucas, fib))
The zip function could raise an exception in this case.
Or it could stop at the shortest iterable.
Or it might go to the longest iterable.
It turns out the zip function stops at the shortest iterable:
>>> list(zip(lucas, fib))
[(2, 1), (1, 1), (3, 2), (4, 3), (7, 5)]
Why?
Well, this was the simplest thing for the Python core developers to implement.
The zip function works by getting an iterator from each of the given iterables, and then repeatedly calling next on each of these iterators.
Once a StopIteration exception is raised by any of the iterators, zip stops:
def zip(*iterables):
iterators = [iter(it) for it in iterables]
while True:
try:
yield tuple([next(it) for it in iterators])
except StopIteration:
return
So the built-in zip function stops at the shortest iterable.
Continuing until the longest iterable
What if we have different length iterables, and we wanted Python to continue until the longest iterable ends?
The zip_longest function from Python's itertools module can do just this:
>>> lucas = [2, 1, 3, 4, 7, 11, 18]
>>> fib = [1, 1, 2, 3, 5]
>>> from itertools import zip_longest
>>> list(zip_longest(lucas, fib))
[(2, 1), (1, 1), (3, 2), (4, 3), (7, 5), (11, None), (18, None)]
The zip_longest function will loop until the longest iterable, filling in missing values with None by default.
But we can customize the fill value that it uses with an optional fillvalue argument:
>>> list(zip_longest(lucas, fib, fillvalue=0))
[(2, 1), (1, 1), (3, 2), (4, 3), (7, 5), (11, 0), (18, 0)]
Raising an exception when iterables have different lengths
Sometimes zipping iterables that have different lengths might indicate a bug in our code.
What if we wanted to raise an exception if we found ourselves zipping iterables that have different lengths?
As of Python 3.10, the zip function accepts an optional strict argument that defaults to False.
When strict is set to True (or a truthy value) it will raise a ValueError exception if the two iterables have different numbers of items.
>>> lucas = [2, 1, 3, 4, 7, 11, 18]
>>> fib = [1, 1, 2, 3, 5]
>>> list(zip(lucas, fib, strict=True))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: zip() argument 2 is shorter than argument 1
It even works for iterables that don't have a length, like generator objects:
>>> letters = "abc"
>>> squares = (n**2 for n in range(10))
>>> list(zip(letters, squares, strict=True))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: zip() argument 2 is longer than argument 1
The strict argument doesn't actually check the length of the given iterables.
It just raises an exception as soon as it discovers that one of the given iterables had more items than the other ones:
>>> letters = "abc"
>>> squares = (n**2 for n in range(10))
>>> for letter, n in zip(letters, squares, strict=True):
... print(letter, n)
...
a 0
b 1
c 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: zip() argument 2 is longer than argument 1
Choose how zip should work with different length iterables
You can use the built-in zip function to loop over multiple iterables at the same time.
If you'd like to make sure your iterables have the same number of items, you can use the strict argument.
If, instead, you'd like to loop until the longest iterable, you can use the zip_longest function from Python's itertools module.
Remember this next month 🧠
Now make sure you remember what you just watched: Daily Recall shows you a few Python questions a day and brings each one back right before you’d forget it.