Kaushik's Blog

Be even more Pythonic with zip's length checking

A recent post on Juha-Matti's blog describes the Python zip() function, used to combine iterables for processing. As described there, using zip() is far more Pythonic than using a variable to index into many iterables.

By default, zip() keeps generating combinations of values until the first iterable runs out. itertools.zip_longest() (which I learned about from the above post), generates combinations until the longest iterable runs out; you can supply a value to be filled in for the shorter iterable.

Most commonly, I don't want the default behaviour of either zip() or itertools.zip_longest(). Rather, I often expect my iterables to be of same length.

This leads to code that invariably performs a check before iterating.

def print_pairs(first, second):
    if len(first) != len(second):
        raise ValueError("Lists must have the same length")

    for x, y in zip(first, second):
        print(f"{x} -> {y}")

Of course, this works as we expected.

>>> print_pairs([1, 2, 3], ['a', 'b', 'c'])
1 -> a
2 -> b
3 -> c
>>> print_pairs([1, 2, 3], ['a', 'b', 'c', 'd'])
Traceback (most recent call last):
...
ValueError: Lists must have the same length

Starting with Python 3.10, zip has an additional argument for optional length checking. This perfectly suits the use case where we want to verify that iterables are of the same length before zipping them.

def print_pairs_better(first, second):
    for x, y in zip(first, second, strict=True):
        print(f"{x} -> {y}")

This raises the same ValueError we coded by hand! But much more Pythonically.

>>> print_pairs_better([1, 2, 3], ['a', 'b', 'c', 'd'])
Traceback (most recent call last):
...
ValueError: zip() argument 2 is longer than argument 1

#python