Функция zip в Python
В Python есть встроенная функция zip, которая позволяет как бы "застегивать" вместе несколько передаваемых ей итерируемых объектов (например, списков). Делает она это путем объединения элементов разных объектов, стоящих в одинаковых позициях.
>>> a = [1, 2, 3, 4] >>> b = ['a', 'b', 'c'] >>> c = ['I', 'II', 'III', 'IV'] >>> for i in zip(a, b, c): ... print(i) ... (1, 'a', 'I') (2, 'b', 'II') (3, 'c', 'III')
Как видно из примера объект, который возвращает функция zip, состоит из кортежей. Первый кортеж объединяет первые элементы переданных в zip списков. Второй – вторые и так далее. Количество кортежей равно длине наименьшего списка.
На самом деле объект типа zip не состоит из кортежей, он их генерирует, так как является объектом-итератором. Это значит, что получить значения из экземпляра zip можно только один раз.
>>> z = zip(a, b) >>> z <zip object at 0x7fc7697a4800> >>> for i in z: ... print(i) ... (1, 'a') (2, 'b') (3, 'c') >>> for i in z: ... print(i) ... >>>
При этом из того, что возвращает zip, можно сразу получить структуру данных, воспользовавшись соответствующей встроенной функцией:
>>> a = [5, 6, 3] >>> b = [1.1, -0.05, 0.3] >>> ab = list(zip(a, b)) >>> ab [(5, 1.1), (6, -0.05), (3, 0.3)] >>> ab = tuple(zip(a, b)) >>> ab ((5, 1.1), (6, -0.05), (3, 0.3))
С помощью zip можно получить так называемую транспонированную матрицу, когда строки становятся столбцами, а столбцы строками.
>>> m = [('a', 'b', 'c'), ('A', 'B', 'C')] >>> m2 = list(zip(*m)) >>> m2 [('a', 'A'), ('b', 'B'), ('c', 'C')] >>> >>> for row in m: ... print(row) ... ('a', 'b', 'c') ('A', 'B', 'C') >>> >>> for row in m2: ... print(row) ... ('a', 'A') ('b', 'B') ('c', 'C')
В примере выражение *m, которое передается в функцию zip, обозначает распаковку списка. В результате в функцию передается не сам список, а два кортежа, в каждом из которых по три элемента. Первые элементы формируют первый кортеж нового объекта, вторые – второй, третьи – третий кортеж.
Если zip-экземпляр присваивать нескольким переменным, то он сразу проитерируется, и каждая переменная получит свой кортеж.
>>> a = [1, 2, 3] >>> b = [-1, -2, -3] >>> x, y, z = zip(a, b) >>> x (1, -1) >>> y (2, -2) >>> z (3, -3)
Понятно, что при этом количество переменных слева от знака присваивания должно совпадать с количеством кортежей в zip-объекте.
Часто кортежи сразу распаковывают в заголовке цикла for. Так делают, если над элементами разных итерируемых объектов надо выполнять однотипные действия или производить операции между ними самими.
>>> qty = [2, 5, 1, 4] >>> price = [100, 91, 34, 15] >>> for q, p in zip(qty, price): ... print(q * p) ... 200 455 34 60 >>> a = [] >>> b = [] >>> for i, j in zip(range(10,20), range(1,10)): ... a.append(i) ... b.append(j) ... >>> a [10, 11, 12, 13, 14, 15, 16, 17, 18] >>> b [1, 2, 3, 4, 5, 6, 7, 8, 9]
Функция zip возвращает итератор, который останавливается, когда исчерпывается самая короткая последовательность. Когда требуется объединить итерируемые объекты разной длины так, чтобы элементы самого длинного не были утеряны, можно воспользоваться функцией zip_longest из модуля itertools.
>>> from itertools import zip_longest >>> >>> a = [1, 2, 3, 4] >>> b = ['a', 'b', 'c'] >>> >>> list(zip_longest(a, b)) [(1, 'a'), (2, 'b'), (3, 'c'), (4, None)] >>> >>> list(zip_longest(a, b, fillvalue='?')) [(1, 'a'), (2, 'b'), (3, 'c'), (4, '?')]
Как мы видим, если элемента не хватает, то по-умолчанию подставляется объект None. Однако с помощью параметра fillvalue можно указать свой вариант заполнения.
Практическая работа
Вычислите суммы столбцов матрицы. Используйте в программе функцию zip.