Объединение итерируемых объектов с помощью zip
Функция zip: основы и варианты использования
Как выполнить параллельную итерацию по нескольким коллекциям?
Основное решение: использование встроенной функции zip(). Она принимает произвольное количество итерируемых объектов и возвращает итератор, порождающий кортежи, состоящие из соответствующих элементов этих объектов. Итерация завершается, когда исчерпан самый короткий из переданных аргументов.
names = ['Анна', 'Борис', 'Виктор']
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f'{name}: {score}')
Python zip (функция zip в python)
Анна: 85 Борис: 92 Виктор: 78
Такой подход удобен для обработки связанных данных, хранящихся в отдельных списках.
Типичная ошибка: если списки имеют разную длину, zip молча обрежет результат до наименьшей длины. Это может привести к потере данных. Выход: использовать itertools.zip_longest (см. один из вариантов ниже).
Как создать словарь из двух списков ключей и значений?
Функция dict(zip(keys, values)) позволяет объединить два списка в словарь. Каждый элемент первого списка становится ключом, а элемент второго списка становится значением.
keys = ['a', 'b', 'c']
values = [1, 2, 3]
result = dict(zip(keys, values))
print(result)
{'a': 1, 'b': 2, 'c': 3}
Этот приём часто применяется при обработке данных из CSV или конфигурационных файлов, где заголовки и строки хранятся отдельно.
Проблема: если списки разной длины, лишние ключи или значения будут отброшены. Для обеспечения полного соответствия следует предварительно выровнять длины с помощью itertools.zip_longest.
Как распаковать список кортежей в отдельные списки?
Обратная операция выполняется с помощью идиомы zip(*list_of_tuples). Она разворачивает кортежи обратно в итераторы, соответствующие каждому элементу кортежа.
pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
numbers, letters = zip(*pairs)
print(list(numbers), list(letters))
[1, 2, 3] ['a', 'b', 'c']
Такой способ удобен для транспонирования матриц, представленных в виде списка строк.
Ошибка: если кортежи разной длины, zip(*) также обрежет до минимальной. Также стоит помнить, что zip возвращает итераторы, поэтому для получения списков требуется явное преобразование.
Как обработать итерации разной длины без потери элементов?
Модуль itertools предоставляет функцию zip_longest, которая продолжает итерацию до исчерпания самого длинного аргумента, заполняя отсутствующие значения указанным fillvalue (по умолчанию None).
from itertools import zip_longest
a = [1, 2, 3]
b = ['x', 'y']
for num, char in zip_longest(a, b, fillvalue='?'):
print(f'{num} -> {char}')
1 -> x 2 -> y 3 -> ?
Это необходимо при работе с данными, где не гарантируется одинаковая длина столбцов, например, при слиянии выборок разного объёма.
Внимание: при наличии большого количества заполнителей может возникнуть неоднозначность, если fillvalue совпадет с реальными данными. Следует выбирать уникальное значение (например, object()).
Как получить индексы элементов при параллельной итерации?
Комбинирование enumerate и zip позволяет одновременно получать порядковый номер и соответствующие элементы нескольких коллекций.
colors = ['red', 'green', 'blue']
codes = ['FF0000', '00FF00', '0000FF']
for i, (color, code) in enumerate(zip(colors, codes), start=1):
print(f'{i}. {color} -> {code}')
1. red -> FF0000 2. green -> 00FF00 3. blue -> 0000FF
Этот приём полезен при выводе нумерованных списков или при обработке данных, где важен порядок.
Нюанс: при большом количестве списков конструкция for i, (a, b, c) in enumerate(zip(...)) может стать громоздкой. В таких случаях лучше вынести распаковку в отдельные переменные.
Расширенные примеры использования функции zip
Пример 1: Транспонирование матрицы (список строк).
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
transposed = list(zip(*matrix))
print(transposed)
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
Здесь оператор распаковки *matrix передаёт каждую строку как отдельный аргумент. zip собирает первые элементы каждой строки, вторые и т.д. В результате получаются кортежи, представляющие столбцы исходной матрицы.
Пример 2: Создание списка кортежей с явным преобразованием в список.
list(zip([1, 2], ['a', 'b'], [True, False]))
[(1, 'a', True), (2, 'b', False)]
По умолчанию zip возвращает итератор. Обёртка в list() материализует все кортежи.
Пример 3: Использование zip с генераторами (ленивые вычисления).
def gen1():
yield from range(5)
def gen2():
yield from 'abcde'
pairs = zip(gen1(), gen2())
for p in pairs:
print(p)
(0, 'a') (1, 'b') (2, 'c') (3, 'd') (4, 'e')
Генераторы вычисляются на лету, что экономит память.
Пример 4: zip с itertools.cycle для создания пар с повторением.
from itertools import cycle
numbers = [1, 2, 3, 4]
letters = cycle(['a', 'b'])
pairs = list(zip(numbers, letters))
print(pairs)
[(1, 'a'), (2, 'b'), (3, 'a'), (4, 'b')]
Циклический итератор автоматически повторяет короткий список.
Пример 5: zip_longest с разными типами заполнителя.
from itertools import zip_longest
a = [1, 2]
b = [10, 20, 30]
c = [100]
result = list(zip_longest(a, b, c, fillvalue=0))
print(result)
[(1, 10, 100), (2, 20, 0), (0, 30, 0)]
Отсутствующие элементы заменяются нулём.
Пример 6: Параллельное чтение двух файлов.
with open('file1.txt') as f1, open('file2.txt') as f2:
for line1, line2 in zip(f1, f2):
print(f'{line1.strip()} | {line2.strip()}')
строка1 | lineA строка2 | lineB
Обратите внимание: если файлы разной длины, zip остановится на меньшем.
Пример 7: Распаковка кортежа с переменным числом элементов (head, *tail).
pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
first, *rest = zip(*pairs)
print(first)
print(rest)
(1, 2, 3)
[('a', 'b', 'c')]
Пример 8: Использование zip для сортировки нескольких списков параллельно.
names = ['C', 'A', 'B']
ages = [30, 20, 25]
sorted_pairs = sorted(zip(names, ages))
sorted_names, sorted_ages = zip(*sorted_pairs)
print(list(sorted_names), list(sorted_ages))
['A', 'B', 'C'] [20, 25, 30]
Сначала создаются пары, затем сортируются по первому элементу (имени). Распаковка возвращает отсортированные списки.