Итераторы и генераторы: вопросы для собеседования (часть 3)

yield, генераторные выражения, протокол итератора (__iter__, __next__) — продвинутый Python, который спрашивают у кандидатов на middle+. Генераторы экономят память при обработке больших данных. На собеседовании просят объяснить ленивое вычисление, написать генератор или показать разницу между списком и генератором.

Коллекции и структуры данныхГенераторы списков и встроенные функцииЦиклы и условияИсключения и отладкаРабота с файлами: JSON и CSVФункции и аргументыNumPy: основыPandas и DataFrameСинтаксис и типы данных

Вопросы 1115 из 20

11Функция-генератор `squares(n)` делает `yield i * i` для `i` из `range(n)`. Чему равно `list(squares(3))`?
A`[1, 4, 9]`
B`[0, 1, 4]`
C`[0, 1, 4, 9]`
D`[0, 2, 4]`
Ответ: `range(n)` выдаёт значения от 0 до `n - 1`, поэтому квадраты считаются для 0, 1 и 2.

`range(3)` — это последовательность `0, 1, 2` (правая граница не включается). Для каждого такого `i` функция-генератор отдаёт `i * i`, то есть `0`, `1`, `4`. Значит `list(squares(3))` равен `[0, 1, 4]`. Если бы интервал начинался с 1 и включал 3, получился бы `[1, 4, 9]`, а удвоение давало бы `[0, 2, 4]` — это уже не квадраты.

Подробный разбор →
12Вы фильтруете большой поток строк `rows`. Почему генератор `def valid(rows): for row in rows: if is_valid(row): yield row` часто экономнее по памяти, чем выражение `[row for row in rows if is_valid(row)]`?
A`yield` сохраняет промежуточные строки во временный файл на диске, освобождая оперативную память.
BГенератор списка нельзя использовать в цикле `for`, в отличие от функции с `yield`.
CГенератор отдаёт строки по одной и не хранит весь набор в памяти, а сразу передаёт следующему шагу обработки.
DФункция-генератор гарантированно работает быстрее списка в любом случае, даже при многократном обходе данных.
Ответ: Генератор лениво выдаёт элементы по одному, не материализуя весь набор в памяти.

Когда `valid(rows)` написана через `yield`, она запускает обработку одной строки за раз: вернула значение, остановилась, ждёт следующего `next()`. Список же сначала собирает все строки целиком в памяти, а затем уже отдаёт их следующему шагу. На больших потоках это и даёт экономию памяти, особенно при чтении из файла или БД. Никакого автоматического сброса на диск `yield` не делает, и по скорости генератор не всегда выигрывает: если данные обходятся несколько раз, заранее построенный список может быть выгоднее.

Подробный разбор →
13Какой объект является итератором и может быть напрямую использован в `next(obj)`?
AИтератор `iter([1, 2])`
BОбычный список `[1, 2]`
CОбычная строка `'ab'`
DОбычное множество `{1, 2}`
Ответ: `next(obj)` работает только с объектами-итераторами; обычные коллекции — это итерируемые, но не итераторы.

`iter([1, 2])` возвращает объект-итератор, у которого есть метод `__next__`, поэтому `next(...)` продвигает его и отдаёт следующий элемент. Список, строка и множество — это итерируемые объекты, к ним можно применить `for`, но прямой `next([1, 2])` поднимет `TypeError`, потому что у них нет состояния «текущей позиции». Чтобы вызвать `next` от них, нужно сначала обернуть их в `iter(...)` и работать с возвращённым итератором.

Подробный разбор →
14Есть `it = iter([1, 2, 3])`. Выполнили `next(it)` два раза, а затем запустили цикл `for x in it: print(x)`. Что будет напечатано?
AБудет напечатано 1, 2, 3.
BБудет напечатано только 3.
CБудет напечатано только 1.
DНичего не будет напечатано.
Ответ: Iterator продолжает с текущей позиции; `next()` уже «съел» первые элементы.

Два вызова `next(it)` последовательно забрали 1 и 2. Цикл `for` продолжит чтение того же iterator и получит только оставшееся значение 3. Это удобно, но может стать багом, если вы не ожидали, что iterator разделяется между `next()` и `for`.

Подробный разбор →
15Дан генератор `def gen(): yield 1; yield 2; yield 3`. Выполнили `g = gen()`, `first = next(g)`, остаток = `list(g)`. Чему равен остаток?
AСписок `[1, 2, 3]`
BСписок `[1, 2]`
CСписок `[3]`
DСписок `[2, 3]`
Ответ: Генератор продолжает работу с того `yield`, на котором остановился, и не возвращается к уже отданным значениям.

После `first = next(g)` генератор отдал `1` и приостановился сразу за первым `yield`. Дальнейший `list(g)` продолжает обход с того же места: следующий `yield 2` даст `2`, потом `yield 3` даст `3`, и генератор завершится. Поэтому остаток равен `[2, 3]`. Уже отданное значение `1` повторно не возвращается, и обход не «обнуляется».

Подробный разбор →
1234

Хотите тренировать интерактивно?

В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.

Тренировать Python в Telegram

Другие темы: Python

Коллекции и структуры данныхГенераторы списков и встроенные функцииЦиклы и условияИсключения и отладкаРабота с файлами: JSON и CSVФункции и аргументыNumPy: основыPandas и DataFrameСинтаксис и типы данных