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?
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')]
zip function works with any iterablesPython'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?
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.
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)]
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
zip should work with different length iterablesYou 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.
We don't learn by reading or watching. We learn by doing. That means writing Python code.
Practice this topic by working on these related Python exercises.
Unlike, JavaScript, C, Java, and many other programming languages we don't have traditional C-style for loops.
Our for loops in Python don't have indexes.
This small distinction makes for some big differences in the way we loop in Python.
To track your progress on this Python Morsels topic trail, sign in or sign up.
Sign in to your Python Morsels account to track your progress.
Don't have an account yet? Sign up here.