enumerate, zip и готовые абстракции itertools/collections
optional заменить ручной range(len(...)) и параллельные индексы на enumerate/zip и подобрать функцию из itertools/collections для типовой задачи
Параллельная итерация по нескольким массивам, обход с индексом и подсчёт с группировкой — задачи, которые в C решаются вручную: общий индекс i для двух массивов, отдельная переменная-счётчик, самописная логика группировки в массив структур. В Python эти паттерны настолько типичны, что закрыты готовыми функциями встроенного набора и стандартной библиотеки; писать их руками — верный признак «C с питоновским синтаксисом».
enumerate: итерация с индексом
Проблема: нужен и индекс, и сам элемент. C-подобный анти-паттерн:
for i in range(len(items)):
print(f"Index: {i}, Value: {items[i]}")
Идиоматично: enumerate оборачивает итерируемый объект и на каждом шаге отдаёт кортеж (индекс, элемент), так что индекс и значение невозможно рассинхронизировать:
for i, item in enumerate(items):
print(f"Index: {i}, Value: {item}")
Начальное значение счётчика задаётся вторым аргументом: enumerate(items, 1) нумерует с единицы — то, что в C вы бы писали как i + 1 внутри цикла.
zip: параллельная итерация
Проблема: нужно идти по нескольким коллекциям одновременно. C-подобный анти-паттерн — общий индекс, который вы сами обязаны согласовать с длинами обоих списков:
for i in range(len(names)): # опасно, если длины не совпадают
print(f"{names[i]} is {ages[i]} years old.")
Идиоматично: zip «сшивает» несколько итерируемых объектов и на каждом шаге отдаёт кортеж из их элементов. zip останавливается, как только заканчивается самая короткая последовательность, — поэтому IndexError из-за разной длины здесь просто невозможен:
for name, age in zip(names, ages):
print(f"{name} is {age} years old.")
Если нужно идти до конца самой длинной последовательности, дополняя недостающие значения, берите itertools.zip_longest. Если нужны сразу индекс и пара значений — enumerate(zip(a, b)), а не два счётчика.
Подсчёт и группировка
Ручной подсчёт «если ключа нет — создать, иначе увеличить» и ручная группировка в стиле «массив структур» тоже уже написаны за вас в модуле collections:
from collections import Counter, defaultdict
counts = Counter(words) # частоты элементов
by_city = defaultdict(list)
for user in users:
by_city[user["city"]].append(user["name"]) # группировка без проверки наличия ключа
defaultdict(list) создаёт пустой список при первом обращении к новому ключу — это заменяет привычную C-проверку «инициализирован ли слот». Обычный dict даёт то же через setdefault, а для чтения с значением по умолчанию есть get.
Общий принцип: перед тем как писать цикл со счётчиками, проверьте, нет ли для этой задачи готовой функции — во встроенных именах (enumerate, zip, sum, min, max, sorted, any, all) или в модулях collections, itertools, functools.
Разобранный пример
Задача: вывести таблицу «номер строки — имя — балл» по двум параллельным спискам.
Перенесённый C-стиль — один индекс на две коллекции:
names = ["Alice", "Bob", "Charlie"]
scores = [90, 75, 82]
for i in range(len(names)):
print(i + 1, names[i], scores[i])
Здесь три источника ошибок: range(len(...)), ручное i + 1 для нумерации с единицы и молчаливое предположение, что scores не короче names.
Идиоматично:
for rank, (name, score) in enumerate(zip(names, scores), 1):
print(rank, name, score)
Ни одной переменной-индекса: zip отвечает за согласование коллекций, enumerate — за нумерацию. Если scores окажется короче, цикл просто закончится раньше, а не упадёт с IndexError на несуществующем индексе.
Попробуй сейчас
Дан код в C-стиле:
temps = [18, 21, 25, 19]
cities = ["Kyiv", "Lviv", "Odesa", "Kharkiv"]
hot = {}
for i in range(len(cities)):
if temps[i] >= 20:
hot[cities[i]] = temps[i]
for i in range(len(cities)):
print(str(i + 1) + ". " + cities[i])
Перепишите оба цикла без range(len(...)): первый — через zip (можно одним dict comprehension), второй — через enumerate с нумерацией с единицы.
Получилось, если…
Вы справились, если:
- первый цикл превратился в
{city: t for city, t in zip(cities, temps) if t >= 20}(или в обычныйfor city, t in zip(...)); - второй — в
for n, city in enumerate(cities, 1): print(n, city); - вы можете объяснить, что произойдёт, если
tempsокажется корочеcities:zipостановится по короткой последовательности, тогда как исходный код обратился бы к несуществующему индексу и упал сIndexError.
Вывод
enumerate даёт индекс без range(len(...)), zip — параллельный обход без общего счётчика и без риска выйти за границы (он останавливается по самой короткой последовательности; для обхода по самой длинной есть itertools.zip_longest). Для подсчёта и группировки берите Counter и defaultdict вместо ручных проверок наличия ключа.
AI-generated · source-grounded review
🛡 Fact-checked: 3 risky claims verified · 1 removed · confidence: medium · a second model checked these claims and agreed
On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course