Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 10 из 20 Начальный 15 мин 100 XP

zip в Python: несколько последовательностей параллельно

Два списка идут параллельно: имена и баллы, товары и цены. zip склеивает их в пары, цикл берёт по одной — без индексов и range(len(...)).

Редакция Питоники

Два списка, которые ходят парой: имена и баллы, товары и цены, города и температуры. Обойти их за один заход — задача каждого второго скрипта. Штатное решение — индексы; у Python есть идиома короче: встроенная функция zip. Разбираем пары, обрезку, строгий режим и словарь из двух списков.

Параллельный проход по индексам

Сначала так, как пишут без zip — через номера позиций:

по индексам
names = ['Аня', 'Борис', 'Вера']
scores = [82, 91, 75]
for i in range(len(names)):
    print(names[i], '-', scores[i])
Вывод
Аня - 82
Борис - 91
Вера - 75

Работает: range(len(names)) даёт номера, по ним достаём оба значения. Но запись шумит: i, i, i, квадратные скобки в каждой строке. Индекс здесь нужен не сам по себе — он лишь способ достать пару значений. Значит, можно обойтись без него.

zip: пары из двух списков

то же самое через zip
names = ['Аня', 'Борис', 'Вера']
scores = [82, 91, 75]
for name, score in zip(names, scores):
    print(name, '-', score)
Вывод
Аня - 82
Борис - 91
Вера - 75

zip взял по одному элементу из каждого списка, склеил пару и отдал циклу, а распаковка в заголовке — for name, score in ... — разложила пару на две переменные. Индексы исчезли: читается по-человечески, для каждого имени и балла — печать.

Посмотрим на zip без цикла — что именно он отдаёт:

пары-кортежи
names = ['Аня', 'Борис', 'Вера']
scores = [82, 91, 75]

pairs = list(zip(names, scores))
print(pairs)
print(pairs[0])
Вывод
[('Аня', 82), ('Борис', 91), ('Вера', 75)]
('Аня', 82)

zip отдаёт пары-кортежи; list разворачивает их в список, чтобы напечатать. Распаковка пар в заголовке цикла — та же, что при переборе словарей в третьем уроке: две переменные через запятую, Python сам разложит кортеж по ним.

Обрезка по самому короткому

списки разной длины
days = ['пн', 'вт', 'ср', 'чт', 'пт']
temps = [21, 19, 23]
for day, temp in zip(days, temps):
    print(day, temp)
Вывод
пн 21
вт 19
ср 23

Пять дней, три температуры — пар вышло три. zip останавливается по самому короткому списку: лишний хвост длинного списка просто не попадает в пары. Ни ошибки, ни предупреждения — python-zip-обрезка работает молча.

Три списка и больше

город, температура, дождь
cities = ['Сочи', 'Казань', 'Пермь']
temps = [24, 17, 8]
rains = [False, True, False]
for city, temp, rain in zip(cities, temps, rains):
    if rain:
        advice = 'зонт'
    else:
        advice = 'очки'
    print(city, temp, '-', advice)
Вывод
Сочи 24 - очки
Казань 17 - зонт
Пермь 8 - очки

Аргументов у zip столько, сколько последовательностей, — и столько же переменных в заголовке цикла. Распаковка работает и здесь: город, температура, дождь — три переменных за одну итерацию.

zip с enumerate: номер и пара

Две задачи накладываются друг на друга чаще, чем кажется: нужны и пары, и номер строки. Нумерованный чек, отчёт с колонкой позиции, список задач с номерами — всё это zip и enumerate в одной строке. Синтаксис выглядит страшнее, чем есть, но разбирается за минуту.

номер, имя и балл
names = ['Аня', 'Борис', 'Вера']
scores = [82, 91, 75]
for i, (name, score) in enumerate(zip(names, scores), start=1):
    print(i, name, score)
Вывод
1 Аня 82
2 Борис 91
3 Вера 75

Читается справа налево. zip склеивает имена с баллами в пары, enumerate нумерует пары, а распаковка в скобках разбирает каждую пару на имя и балл. Скобки здесь обязательны: без них Python попытается разложить сразу три значения, а пара — это одна величина.

Словарь из двух списков

dict из пар
goods = ['молоко', 'хлеб', 'сыр']
prices = [89, 42, 590]

menu = dict(zip(goods, prices))
print(menu)
print(menu['сыр'])
Вывод
{'молоко': 89, 'хлеб': 42, 'сыр': 590}
590

Связка dict(zip(...)) собирает словарь одним вызовом: список ключей и список значений превращаются в пары, пары — в словарь. Так колонки файла или формы превращаются в словарь за одну строку; о словарях подробнее — в разделе про словари.

Обрезка внутри dict(zip(...))

Связка dict(zip(keys, values)) выглядит безобидно, но унаследовала все свойства zip, включая тихую обрезку. Если ключей больше, чем значений, лишние ключи просто не попадут в словарь — и никакого сообщения об этом не будет.

словарь из трёх ключей и двух значений
keys = ['a', 'b', 'c']
vals = [1, 2]
d = dict(zip(keys, vals))
print(d)
Вывод
{'a': 1, 'b': 2}

Ключа 'c' в словаре нет, и zip не сказал ни слова: пара просто не была создана. Для данных из формы или CSV это означает потерю целой записи, причём заметить её можно только по итогам. Если равные длины обязательны, проверяйте их до сборки: if len(keys) != len(values): — и разбирайтесь с расхождением.

zip одноразовый

второй проход пуст
a = [1, 2, 3]
b = [4, 5, 6]

pairs = zip(a, b)
print(list(pairs))
print(list(pairs))
Вывод
[(1, 4), (2, 5), (3, 6)]
[]

Второй print напечатал пустые скобки: zip — итератор, пары вынимаются из него один раз, как вода из бутылки. Понадобился второй проход — соберите пары в список заранее: pairs = list(zip(a, b)) — и ходите по pairs сколько угодно раз.

Для нумерованного параллельного прохода zip объединяется с enumerate: enumerate(zip(names, scores), start=1) отдаёт номер и пару, а пара распаковывается в скобках: for i, (name, score) in .... С индексами через range так аккуратно не получится — а для сумм по парам пригодится урок про аккумуляторы.

параллельный ввод от пользователя
# Имена и баллы вводит пользователь
names = input('Имена через пробел: ').split()
scores = input('Баллы через пробел: ').split()

for name, score in zip(names, scores):
    print(name, '-', score)
Блок с input() в песочнице не запустится — ввода там нет. Локально введите, например: Аня Борис и 82 91 — zip сам разложит ввод по парам.

zip в расчётах

Пара удобна тем, что её не нужно разбирать: два значения пришли вместе и работают вместе. Классика — цена и количество: сумма заказа считается прямо в цикле, без обращения к спискам по индексу.

сумма по позициям заказа
prices = [89, 42, 590]
qty = [2, 1, 1]
total = 0
for p, q in zip(prices, qty):
    total = total + p * q
print(total)
Вывод
810

810 — это 89 × 2 плюс 42 × 1 плюс 590 × 1. Та же арифметика через range(len(...)) заняла бы больше места и выглядела бы хуже, а ошибка в индексе стоила бы дороже. Единственное, о чём стоит помнить: если длины списков разные, zip возьмёт меньшую и часть позиций не попадёт в расчёт.

zip работает и со строками — символы одной строки склеиваются с символами другой:

две строки парами
letters = ['а', 'б', 'в']
numbers = [1, 2, 3]
for ch, n in zip(letters, numbers):
    print(f'{n}) {ch}')
Вывод
1) а
2) б
3) в

Здесь пары склеиваются из разнородных наборов: буквы и номера. Ничего сверхъестественного в zip нет — это просто способ пройти по нескольким последовательностям синхронно, держа в руках по одному элементу из каждой.

Что выбрать: индексы, zip или пара с номером

Три способа пройти по двум спискам дают одинаковый результат, и выбор между ними — вопрос вкуса, а не правоты. Моя рекомендация: берите zip, когда вам нужны сами значения, и добавляйте enumerate только тогда, когда номер действительно используется в выводе или в логике. Лишний номер в заголовке — это лишний код, который придётся читать.

ЗадачаЧто писатьПочему
Значения из двух списковfor a, b in zip(x, y)пары приходят готовыми, индексы не нужны
Значения и их номераfor i, (a, b) in enumerate(zip(x, y), start=1)номер нужен для вывода или отчёта
Только индексыfor i in range(len(x))значения по номеру не нужны
Словарь из двух списковdict(zip(keys, values))пары превращаются в словарь одним вызовом
Разные длины, терять нельзяzip_longest из itertoolsхвост добивается, а не отбрасывается

Правило простое: индекс — это способ достать значение, а не цель. Если значение можно получить сразу, берите zip. Возвращаться к range(len(...)) стоит только там, где нужен именно номер — например, чтобы обратиться к третьему списку по той же позиции.

range(len(...))zip
Что перебираемномера, значения достаём по нимпары значений сразу
Три списка и большескобки растут: a[i], b[i], c[i]столько же переменных в заголовке
Разные длиныIndexError по длинному спискутихая обрезка по короткому
Читаемостьмеханическая, через индексыидиоматичная, без индексов
Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

a = [1, 2, 3]
b = ['х', 'у']
for x, y in zip(a, b):
    print(x, y)
keys = ['a', 'b', 'c']
vals = [1, 2, 3]
d = dict(zip(keys, vals))
print(d['b'] + d['c'])
z = zip([1, 2], [3, 4])
print(list(z))
print(list(z))
Проверь себя
0 / 8

1. Сколько пар отдаст zip для списков длиной 5 и 2?

2. Как увидеть пары zip обычным списком?

3. Что получит второй цикл по одному и тому же объекту zip?

4. Что делает zip(a, b, strict=True) в Python 3.10 и новее?

5. Как выглядит заголовок цикла для трёх списков через zip?

6. Как получить и номер строки, и пару из двух списков?

7. Что произойдёт с dict(zip(keys, values)), если ключей больше, чем значений?

8. Зачем в цикле по парам нужен аккумулятор?

Карточки терминов
Запомнено: 0 / 8
Практика

Даны списки subjects = ['матем', 'физика'] и grades = [5, 4]. Соберите словарь journal через dict и zip, выведите словарь целиком, затем выведите оценку по физике по ключу.

practice.py
Вопросы и ответы по уроку

Что делает zip в Python простыми словами?

Склеивает несколько списков в пары: берёт по одному элементу из каждого списка и отдаёт их вместе. Цикл for name, score in zip(names, scores) за одну итерацию получает и имя, и балл — без индексов и range(len(...)).

Почему zip теряет элементы из длинного списка?

Он отдаёт пары, пока хватает элементов в самом коротком списке, — так устроен по умолчанию. Если равные длины обязательны, включите zip(a, b, strict=True) с Python 3.10: несовпадение длин уронит ValueError вместо тихой потери данных.

Как пройти по двум спискам и всё же получить индексы?

Объединить enumerate и zip: for i, (name, score) in enumerate(zip(names, scores)) — номер и пара за одну итерацию. Нумерация начинается с нуля, а start=1 превратит её в обычный счёт с единицы.

Урок 4: enumerate

Чем zip отличается от zip_longest?

zip_longest из стандартного модуля itertools не обрезает, а добивает короткий список значением fillvalue (по умолчанию None): пары идут до конца самого длинного списка. Выбирайте zip для равных по смыслу длин и zip_longest, когда терять хвост нельзя.

Как пронумеровать пары из двух списков?

Соединить zip и enumerate: for i, (name, score) in enumerate(zip(names, scores), start=1). Скобки вокруг пары обязательны, иначе Python не сможет разложить три значения.

Урок 4: enumerate

Почему словарь из zip может не совпасть с исходными данными?

zip обрезает по самому короткому списку, и dict(zip(keys, values)) ведёт себя так же: лишние ключи просто не попадут в словарь, без всякого сообщения. Проверяйте длины или включайте strict=True.

Понравился урок? Сошлитесь на него

«zip останавливается по самому короткому списку: лишний хвост длинного списка просто не попадает в пары.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.