zip(*iterables, strict=False)
*iterables
- последовательность аргументов или итераций.strict=False
- отвечает за проверку длин переданных итераций. Доступен с версии Python 3.10.Функция zip()
создает итератор кортежей, который объединяет элементы каждой из переданных последовательностей *iterables
.
Пример:
>>> for item in zip([1, 2, 3], ['sugar', 'spice', 'everything nice']): ... print(item) # (1, 'sugar') # (2, 'spice') # (3, 'everything nice')
Более формально: функция zip()
возвращает итератор кортежей, где i-й кортеж содержит i-й элемент из каждой итерации аргументов.
Другой способ понять функцию zip()
состоит в том, что она превращает строки в столбцы, а столбцы в строки. Это похоже на транспонирование матрицы.
Функция zip()
ленива: элементы не будут обрабатываться, пока не будет повторена итерация, например циклом for/in
или заключением в список list(zip())
.
Следует учитывать, что итерации, передаваемые в zip()
, могут иметь разную длину, иногда намеренно, а иногда из-за ошибки в коде. Python предлагает три разных подхода к решению этой проблемы:
По умолчанию функция zip()
останавливается, когда исчерпывается самая короткая итерация. Она проигнорирует оставшиеся элементы в более длинных итерациях, обрезая результат до длины самой короткой итерации:
>>> list(zip(range(3), ['fee', 'fi', 'fo', 'fum'])) # [(0, 'fee'), (1, 'fi'), (2, 'fo')]
Функция zip()
часто используется в тех случаях, когда предполагается, что итерации имеют одинаковую длину. В таких случаях рекомендуется использовать параметр strict=True
(доступен с версии Python 3.10). Вывод будет такой же, как и у обычного zip()
:
>>> list(zip(('a', 'b', 'c'), (1, 2, 3), strict=True)) # [('a', 1), ('b', 2), ('c', 3)]
В отличие от поведения по умолчанию параметр strict=True
(доступен с версии Python 3.10), проверяет идентичность длин итераций, вызывая ошибку ValueError
, если они не совпадают:
>>> list(zip(range(3), ['fee', 'fi', 'fo', 'fum'], strict=True)) # Traceback (most recent call last): # ... # ValueError: zip() argument 2 is longer than argument 1
Без аргумента strict=True
(доступен с версии Python 3.10), любая ошибка, приводящая к итерациям разной длины, будет заглушена, что может проявиться как трудно обнаруживаемая ошибка в другой части программы.
Более короткие итерации можно дополнить постоянным значением, чтобы все итерации имели одинаковую длину. Это делает itertools.zip_longest()
.
Пограничные случаи: с одним итерируемым аргументом, функция zip()
возвращает итератор из кортежей с одним элементом. Без аргументов он возвращает пустой итератор.
Секреты и уловки:
Порядок оценки итераций слева направо гарантируется. Это делает возможной идиому для кластеризации ряда данных в группы длиной n
с использованием zip(*[iter(s)]*n, strict=True)
. Это повторяет один и тот же итератор n
раз, так что каждый выходной кортеж имеет результат n
вызовов итератора. Это приводит к разделению входных данных на блоки длиной n
.
zip()
в сочетании с оператором *
можно использовать для распаковки списка:
>>> x = [1, 2, 3] >>> y = [4, 5, 6] >>> list(zip(x, y)) # [(1, 4), (2, 5), (3, 6)] >>> x2, y2 = zip(*zip(x, y)) >>> x == list(x2) and y == list(y2) # True
Изменено в версии 3.10: Добавлен аргумент strict
.
zip()
zip()
в циклах for/in
;Совместно с оператором распаковки *
в аргументах функции zip()
можно использовать распаковку списка кортежей на отдельные списки:
>>> x = [1, 2, 3] >>> y = [4, 5, 6] # объединим два списка >>> zipped = zip(x, y) >>> list(zipped) # [(1, 4), (2, 5), (3, 6)] # распакуем полученный список кортежей >>> x2, y2 = zip(*zipped) # сравниваем полученные списки # с их исходными значениями >>> list(x2) == x and list(y2) == y # True
Допустим, что есть несколько списков, которые связаны между собой по индексам и их нужно отсортировать, при этом не нарушив связей.
Рассмотрим такую ситуацию на примере из трех связанных списка, в этом случае связь означает, что нулевой элемент первого списка связан с нулевым элементом второго списка, и с нулевым элементом третьего списка, далее первый элемент первого списка связан с первым элементом второго списка, и с первым элементом третьего списка и т.д.
Следовательно сортировать списки по отдельности нельзя, т.к. нарушиться связь. Смотрим как можно сортировать такие списки, используя функцию zip()
>>> from random import shuffle # приготовим первый список >>> x = list(range(6)) # перемешаем его >>> shuffle(x) # приготовим второй список >>> y = list(range(6)) # третий список пусть будет перевернутый `y` >>> z = list(reversed(y)) # распечатаем для наглядности >>> print(f'x: {x}\ny: {y}\nz: {z}') # x: [3, 5, 2, 1, 4, 0], # y: [0, 1, 2, 3, 4, 5], # z: [5, 4, 3, 2, 1, 0]) # сортируем по первому списку `x` >>> x, y, z = zip(*sorted(zip(x, y, z), key=lambda tpl: tpl[0])) >>> print(f'{list(x)}\n{list(y)}\n{list(z)}') # [0, 1, 2, 3, 4, 5] # [5, 3, 2, 0, 4, 1] # [0, 2, 3, 5, 1, 4] # теперь сортируем по последнему списку `z` # обратите внимание, что меняется только # аргумент `key` в функции `sorted()` >>> x, y, z = zip(*sorted(zip(x, y, z), key=lambda tpl: tpl[2])) >>> print(f'{list(x)}\n{list(y)}\n{list(z)}') # [0, 4, 1, 2, 5, 3] # [5, 4, 3, 2, 1, 0] # [0, 1, 2, 3, 4, 5]
zip()
в циклах for/in
.Есть такие ситуации, когда необходимо перебрать несколько списков в одном цикле for/in
. Первое, что приходит в голову, это вытаскивать элементы этих списков в цикле по индексу, как то так:
for i in range(len(list1)) a, b, c = list1[i], list2[i], list3[i] # или for i, a in enumerate(list1) b, c = list2[i], list3[i]
Но есть способ проще и эффектнее с использованием функции zip()
:
>>> lst1 = [0, 1, 2, 3, 4, 5] >>> lst2 = [5, 3, 2, 0, 4, 1] >>> lst3 = ['zero', 'one', 'two', 'three', 'four', 'five'] >>> for a, b, c in zip(lst1, lst2, lst3): ... print(f'{c}:\t{a} + {b} = {a + b}') ... # zero: 0 + 5 = 5 # one: 1 + 3 = 4 # two: 2 + 2 = 4 # three: 3 + 0 = 3 # four: 4 + 4 = 8 # five: 5 + 1 = 6
>>> lst1 = [0, 1, 2, 3, 4, 5] >>> lst2 = ['zero', 'one', 'two', 'three', 'four', 'five'] # создаем словарь при помощи `dict()` >>> dict(zip(lst1, lst2)) # {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'} # создаем словарь при помощи выражения генератора # словаря, при этом меняем элементы списков местами >>> d = {y: x for x, y in zip(lst1, lst2)} >>> d # {'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}