Что такое генераторы и чем они отличаются от обычных функций и списков?
Короткий ответ
- Генератор — функция с yield, возвращает ленивый итератор
- Значения вычисляются по одному при вызове next()
- Состояние функции замораживается между yield
- Экономит память: не держит всю коллекцию целиком
- Генератор можно обойти только один раз
- Генераторные выражения — (x for x in ...) вместо списка
Генератор лениво выдаёт значения по одному, сохраняя состояние между вызовами, что экономит память на больших последовательностях.
Как сказать вслух
пример ответаГенератор — это функция с yield: при вызове она не выполняется сразу, а возвращает объект, который выдаёт значения по одному. Между выдачами состояние функции сохраняется. Главный плюс — экономия памяти: можно обрабатывать файл на гигабайты, не загружая его целиком. Важно помнить, что генератор одноразовый — второй раз по нему не пройтись.
Подробный ответ
Основной ответ
Функция с yield при вызове возвращает объект-генератор, реализующий протокол итератора (__iter__ и __next__). Тело начинает исполняться только при первом next() и приостанавливается на каждом yield, сохраняя локальные переменные и позицию. Это даёт ленивые вычисления: элементы создаются по запросу, память не растёт с размером данных. Генераторные выражения (x*2 for x in data) — компактная форма. Генератор исчерпывается один раз, после чего поднимает StopIteration. Дополнительно есть yield from для делегирования вложенному генератору и методы send()/close() — на их основе исторически строились корутины.
Ключевые моменты
- Протокол итератора. Итерируемый объект отдаёт итератор через __iter__; итератор выдаёт значения через __next__ и поднимает StopIteration в конце.
- Ленивость и память. Список на 10 млн элементов занимает память целиком, генератор — O(1): хранится только текущее состояние.
- Одноразовость. После полного обхода генератор пуст; чтобы пройтись снова, нужно создать его заново.
- yield from. Делегирует итерацию вложенному генератору и пробрасывает send/throw, упрощая композицию.
Практический контекст
В работе генераторы встречаются при чтении больших файлов построчно, стриминге ответов API, пайплайнах обработки данных и в ORM (QuerySet.iterator() в Django). Интервьюер проверяет понимание ленивости и типичный вопрос-ловушку: «что выведет повторный проход по генератору». Полезно показать, что вы знаете, когда генератор не нужен — если данные маленькие и нужны несколько раз, список проще.
Пример кода
def read_large_file(path):
with open(path, encoding="utf-8") as f:
for line in f:
yield line.strip()
lines = read_large_file("big.log")
errors = (l for l in lines if "ERROR" in l)
for e in errors:
print(e) # файл читается построчно, память O(1)Частые ошибки
- Пытаются обойти генератор дважды и ждут те же значения
- Говорят, что функция с yield «возвращает список»
- Не могут объяснить разницу между итерируемым объектом и итератором