yield, генераторные выражения, протокол итератора (__iter__, __next__) — продвинутый Python, который спрашивают у кандидатов на middle+. Генераторы экономят память при обработке больших данных. На собеседовании просят объяснить ленивое вычисление, написать генератор или показать разницу между списком и генератором.
Всего в этом разделе 20 вопросов. Каждый — с правильным ответом и кратким разбором теории. Разбито на 4 части по 5 вопросов.
В отличие от list comprehension `[x * x for x in xs]`, генераторное выражение `(x * x for x in xs)` не вычисляет все значения сразу и не хранит их в списке. Оно возвращает объект генератора, который отдаёт элементы по мере запроса через `next()` или в цикле `for`.
После `it = iter(['a', 'b'])` первый `next(it)` вернёт `'a'`. Второй `next(it)` вернёт `'b'`, а следующий вызов уже приведёт к `StopIteration`.
Итератор по пустому списку не содержит элементов. Первый же вызов `next(it)` не может вернуть значение и поднимает `StopIteration`. Именно это исключение использует цикл `for`, чтобы понять, что итерация завершилась.
Строка `g = (f(x) for x in [1, 2, 3])` создаёт объект генератора, но не запускает вычисление элементов. `f(x)` будет вызываться только когда вы начнёте читать из `g` — например, через `next(g)`, `list(g)` или цикл `for x in g`.
Вызов `list(it)` последовательно берёт элементы через `next(it)` до конца. Когда элементы заканчиваются, внутри используется `StopIteration`, но наружу исключение не пробрасывается. Итератор остаётся в состоянии «конец», поэтому второй `list(it)` возвращает пустой список.
В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.
Тренировать в Telegram