Итераторы и генераторы: вопросы для собеседования (часть 2)
yield, генераторные выражения, протокол итератора (__iter__, __next__) — продвинутый Python, который спрашивают у кандидатов на middle+. Генераторы экономят память при обработке больших данных. На собеседовании просят объяснить ленивое вычисление, написать генератор или показать разницу между списком и генератором.
Вопросы 6–10 из 20
6Дан генератор: `def gen(): print('A'); yield 1; print('B'); yield 2`. Сразу после `g = gen()` что будет выведено в консоль?
AВыведется `A`.
BВыведутся `A` и `B`.
CНичего не выведется, пока не начать итерацию (например, `next(g)`).
DБудет выброшено исключение `TypeError`.
Ответ: Код внутри генератора выполняется только при итерации, до первого `yield`.
Вызов `g = gen()` создаёт объект генератора, но не запускает тело функции. Тело начнёт выполняться при `next(g)` или при цикле `for x in g`. Поэтому до начала итерации ничего не печатается.
7Функция-генератор `def evens(xs): for x in xs: if x % 2 == 0: yield x`. Чему равен результат `list(evens([1, 2, 3, 4]))`?
A`[1, 2, 3, 4]`
B`[1, 3]`
C`[2, 4]`
D2
Ответ: `yield` отдаёт только подходящие значения, а `list(...)` материализует генератор.
Генератор проходит по `xs` и делает `yield x` только для чётных `x`. Поэтому при входе `[1, 2, 3, 4]` будут отданы 2 и 4. Оборачивание в `list(...)` просто собирает все выданные элементы в список.
8Что обычно верно про `(row for row in rows)` по сравнению с `[row for row in rows]`, если `rows` — большой поток данных?
AОба создают список в памяти одинакового размера.
BГенераторное выражение всегда быстрее списка.
CСписок нельзя итерировать, а генератор можно.
DГенераторное выражение вычисляет элементы лениво и обычно экономит память, но его результат одноразовый.
Ответ: Генератор — ленивый и потоковый, список — материализованный и подходит для повторных проходов.
List comprehension `[row for row in rows]` создаёт весь список сразу и хранит его в памяти. Generator expression `(row for row in rows)` возвращает итератор, который выдаёт элементы по мере запроса, поэтому чаще экономит память на больших данных. Но генератор нельзя «перемотать»: после одного прохода он исчерпывается.
9Есть `it = iter([5])`. Выполнили `next(it)`, а затем `next(it, -1)`. Что вернёт второй вызов `next(it, -1)`?
A5
B-1
C`None`
DИсключение `StopIteration`.
Ответ: `next(it, default)` возвращает `default` вместо `StopIteration`, если iterator исчерпан.
После первого `next(it)` единственный элемент 5 уже прочитан. Второй вызов `next(it, -1)` не может получить новое значение, но вместо исключения вернёт значение по умолчанию — -1. Это удобно, когда хотите безопасно попытаться взять элемент без обработки `StopIteration`.
10Создали `g = (x for x in [1, 2, 3])`, затем `s1 = sum(g)` и `s2 = sum(g)`. Чему равно `s2`?
A6
B3
C0
DБудет ошибка `TypeError`.
Ответ: Generator expression исчерпывается после первого прохода; второй раз `sum` увидит пустой поток.
При первом `sum(g)` генератор отдаёт 1, 2, 3 и исчерпывается. Второй `sum(g)` суммирует уже пустой iterator, а сумма пустой последовательности в Python — 0. Это типичный баг в аналитике: один и тот же генератор используют для нескольких метрик.
Хотите тренировать интерактивно?
В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.
Тренировать в Telegram