Перейти к содержимому
Теория · Кортежи и распаковка · урок 5 из 8

enumerate и zip

Представьте гардероб в театре. Гардеробщик принимает пальто и к каждому выдаёт номерок: первому пальто — номер 1, второму — номер 2. У каждого зрителя на руках пара: номерок и его пальто.

В программах такая пара нужна постоянно. Цикл for name in names выдаёт элементы списка, но не их номера. Номер приходится вести в отдельной переменной или перебирать индексы через range(len(names)). Функция enumerate работает как гардеробщик и выдаёт номер вместе с элементом:

Python
names = ["ann", "bob", "cid"]
for i, name in enumerate(names, 1):
    print(f"{i}.{name}", end=" ")  # 1.ann 2.bob 3.cid

Давайте разберём заголовок цикла:

  1. enumerate(names, 1) выдаёт пары (1, 'ann'), (2, 'bob') и (3, 'cid').
  2. for i, name in распаковывает каждую пару: номер попадает в i, имя — в name.
  3. Тело цикла печатает номер и имя через точку.

Пары из enumerate

На каждой итерации enumerate выдаёт кортеж из двух элементов: номер и элемент. Что важно знать про эти пары:

  • заголовок for i, name in распаковывает кортеж в два имени, как обычное присваивание;
  • второй аргумент задаёт первый номер, а без него счёт идёт с 0, как у индексов;
  • enumerate принимает любой итерируемый объект: enumerate("ab") даёт пары (0, 'a') и (1, 'b').

print(enumerate(names)) не показывает пары, а печатает строку вида <enumerate object at 0x…>. Здесь аналогия с гардеробом перестаёт работать. Гардеробщик раздаёт номерки сразу всем, а enumerate выдаёт пары по одной, только когда цикл их просит.

Функция list() собирает все пары в список, который можно напечатать и индексировать:

Python
numbered = list(enumerate(names))
print(numbered)  # [(0, 'ann'), (1, 'bob'), (2, 'cid')]
print(numbered[-1][0])  # 2

numbered[-1] — последний кортеж списка, (2, 'cid'), а [0] берёт из него номер.

zip

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

Python
scores = [90, 75]
pairs = list(zip(names, scores))
print(pairs)  # [('ann', 90), ('bob', 75)]
for name, score in zip(names, scores):
    print(name, score, end="; ")  # ann 90; bob 75;

Что происходит в примере:

  • zip(names, scores) сцепляет имя с оценкой под тем же индексом;
  • list() собирает пары в список, как и в случае с enumerate;
  • цикл for name, score in распаковывает каждую пару в два имени.

У настоящей молнии половинки одной длины, а у списков — не обязательно. zip останавливается, когда кончается самая короткая последовательность. Ошибки при этом нет: у "cid" нет оценки, и в пары это имя не попадает.

Об этом часто спрашивают на собеседовании: «Что сделает zip со списками разной длины?» Ответ: молча отбросит лишние элементы. Если такая потеря означает ошибку в данных, длины сравнивают до цикла: len(names) == len(scores). Другой способ — zip(names, scores, strict=True): тогда при разной длине Python сообщает об ошибке ValueError.

zip принимает и три последовательности, и больше: тогда в каждом кортеже по элементу из каждой. Готовый список пар тоже перебирают с распаковкой: цикл for name, score in pairs: даёт те же имена и оценки, что и цикл по zip.

Частая ошибка

Две последовательности перечислили в заголовке for через запятую, без zip:

Python
names = ["ann", "bob", "cid"]
scores = [90, 75, 60]
for name, score in names, scores:  # ✗
    print(name, score)
TEXT
ValueError: too many values to unpack (expected 2)

names, scores — кортеж из двух списков, и цикл перебирает списки, а не их элементы. На первой итерации он пытается распаковать в два имени весь список names, а в нём три элемента.

Будь в каждом списке по два элемента, ошибки бы не было. Но тогда name и score получили бы сначала два имени, потом две оценки, а не имя с его оценкой. Верный заголовок — for name, score in zip(names, scores):.

Главное

  • enumerate выдаёт пары «номер, элемент»; второй аргумент задаёт первый номер, по умолчанию 0.
  • zip сцепляет элементы нескольких последовательностей с одинаковым индексом в кортежи.
  • zip останавливается на самой короткой последовательности без ошибки; strict=True превращает разницу длин в ValueError.
  • Пары из enumerate и zip раскладывают по именам прямо в заголовке for.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом