enumerate и zip
Представьте гардероб в театре. Гардеробщик принимает пальто и к каждому выдаёт номерок: первому пальто — номер 1, второму — номер 2. У каждого зрителя на руках пара: номерок и его пальто.
В программах такая пара нужна постоянно. Цикл for name in names выдаёт элементы списка, но не их номера. Номер приходится вести в отдельной переменной или перебирать индексы через range(len(names)). Функция enumerate работает как гардеробщик и выдаёт номер вместе с элементом:
names = ["ann", "bob", "cid"]
for i, name in enumerate(names, 1):
print(f"{i}.{name}", end=" ") # 1.ann 2.bob 3.cidДавайте разберём заголовок цикла:
enumerate(names, 1)выдаёт пары(1, 'ann'),(2, 'bob')и(3, 'cid').for i, name inраспаковывает каждую пару: номер попадает вi, имя — вname.- Тело цикла печатает номер и имя через точку.
Пары из enumerate
На каждой итерации enumerate выдаёт кортеж из двух элементов: номер и элемент. Что важно знать про эти пары:
- заголовок
for i, name inраспаковывает кортеж в два имени, как обычное присваивание; - второй аргумент задаёт первый номер, а без него счёт идёт с
0, как у индексов; enumerateпринимает любой итерируемый объект:enumerate("ab")даёт пары(0, 'a')и(1, 'b').
print(enumerate(names)) не показывает пары, а печатает строку вида <enumerate object at 0x…>. Здесь аналогия с гардеробом перестаёт работать. Гардеробщик раздаёт номерки сразу всем, а enumerate выдаёт пары по одной, только когда цикл их просит.
Функция list() собирает все пары в список, который можно напечатать и индексировать:
numbered = list(enumerate(names))
print(numbered) # [(0, 'ann'), (1, 'bob'), (2, 'cid')]
print(numbered[-1][0]) # 2numbered[-1] — последний кортеж списка, (2, 'cid'), а [0] берёт из него номер.
zip
Функция zip названа в честь застёжки-молнии. Когда молнию застёгивают, зубцы двух половинок сцепляются попарно: первый с первым, второй со вторым. Так и zip идёт по нескольким последовательностям сразу и на каждом шаге выдаёт кортеж из их элементов с одинаковым индексом:
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:
names = ["ann", "bob", "cid"]
scores = [90, 75, 60]
for name, score in names, scores: # ✗
print(name, score)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 и подпишись на канал — откроются все уроки.