Сообщить об ошибке.

Функция zip() в Python, объединить элементы в список кортежей

Объединяет элементы последовательностей в список кортежей

Синтаксис:

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 предлагает три разных подхода к решению этой проблемы:

  1. По умолчанию функция zip() останавливается, когда исчерпывается самая короткая итерация. Она проигнорирует оставшиеся элементы в более длинных итерациях, обрезая результат до длины самой короткой итерации:

    >>> list(zip(range(3), ['fee', 'fi', 'fo', 'fum']))
    # [(0, 'fee'), (1, 'fi'), (2, 'fo')]
    
  2. Функция 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), любая ошибка, приводящая к итерациям разной длины, будет заглушена, что может проявиться как трудно обнаруживаемая ошибка в другой части программы.

  3. Более короткие итерации можно дополнить постоянным значением, чтобы все итерации имели одинаковую длину. Это делает 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() можно использовать распаковку списка кортежей на отдельные списки:

>>> 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}