Функция zip() появилась ещё в Python 2 (тогда возвращала список), а с Python 3 возвращает ленивый итератор. Она "сшивает" несколько последовательностей в кортежи поэлементно, что сделало обработку параллельных итерируемых коллекций удобной и эффективной.
Нередко нужно обработать несколько списков (или другого рода последовательности) одновременно — например, пройтись по паре ключ-значение или обработать координаты точка-пара. Самостоятельная синхронизация индексов — источник ошибок и нечитабельности кода, особенно для коллекций разной длины.
Функция zip() принимает любое количество итерируемых объектов и возвращает итератор кортежей, в каждом из которых взяты соответствующие элементы каждого итерируемого. Если последовательности разной длины, результат обрывается на самой короткой.
names = ['Alice', 'Bob', 'Charlie'] ages = [24, 27, 30] for name, age in zip(names, ages): print(f'{name} is {age} years old')
Можно развернуть zip с помощью *:
pairs = [(1, 'a'), (2, 'b'), (3, 'c')] nums, chars = zip(*pairs) print(nums) # (1, 2, 3) print(chars) # ('a', 'b', 'c')
Что будет, если передать zip() коллекции разной длины?
zip() остановится, когда достигнет конца самой короткой коллекции — остальные элементы длинных коллекций игнорируются.
print(list(zip([1,2,3], ['a','b']))) # [(1, 'a'), (2, 'b')]
Как получить кортежи, дополняя более короткие последовательности значением по умолчанию?
Стандартная zip() так не умеет, но есть itertools.zip_longest для такого поведения:
from itertools import zip_longest for a, b in zip_longest([1,2], ['x','y','z'], fillvalue=None): print(a, b) # 1 x # 2 y # None z
Можно ли "распаковать" результат zip(), чтобы снова получить исходные списки?
Да, если все исходные коллекции были одной длины и результат не был изменён, оператор * позволяет развернуть zip.
pairs = [(1,2), (3,4)] a, b = zip(*pairs) print(a) # (1, 3) print(b) # (2, 4)
Обработка связанных коллекций разной длины, не учитывая особенности zip:
lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip(lst1, lst2): print(x, y) # 1 a # 2 b # (3,4) и 'c', 'd' из lst1 не обработались
Плюсы:
Минусы:
Использование zip_longest с fillvalue, чтобы не потерять ни один элемент:
from itertools import zip_longest lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip_longest(lst1, lst2, fillvalue='?'): print(x, y) # 1 a # 2 b # 3 ? # 4 ?
Плюсы:
Минусы: