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}