yield, генераторные выражения, протокол итератора (__iter__, __next__) — продвинутый Python, который спрашивают у кандидатов на middle+. Генераторы экономят память при обработке больших данных. На собеседовании просят объяснить ленивое вычисление, написать генератор или показать разницу между списком и генератором.
`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]` — это уже не квадраты.
Подробный разбор →Когда `valid(rows)` написана через `yield`, она запускает обработку одной строки за раз: вернула значение, остановилась, ждёт следующего `next()`. Список же сначала собирает все строки целиком в памяти, а затем уже отдаёт их следующему шагу. На больших потоках это и даёт экономию памяти, особенно при чтении из файла или БД. Никакого автоматического сброса на диск `yield` не делает, и по скорости генератор не всегда выигрывает: если данные обходятся несколько раз, заранее построенный список может быть выгоднее.
Подробный разбор →`iter([1, 2])` возвращает объект-итератор, у которого есть метод `__next__`, поэтому `next(...)` продвигает его и отдаёт следующий элемент. Список, строка и множество — это итерируемые объекты, к ним можно применить `for`, но прямой `next([1, 2])` поднимет `TypeError`, потому что у них нет состояния «текущей позиции». Чтобы вызвать `next` от них, нужно сначала обернуть их в `iter(...)` и работать с возвращённым итератором.
Подробный разбор →Два вызова `next(it)` последовательно забрали 1 и 2. Цикл `for` продолжит чтение того же iterator и получит только оставшееся значение 3. Это удобно, но может стать багом, если вы не ожидали, что iterator разделяется между `next()` и `for`.
Подробный разбор →После `first = next(g)` генератор отдал `1` и приостановился сразу за первым `yield`. Дальнейший `list(g)` продолжает обход с того же места: следующий `yield 2` даст `2`, потом `yield 3` даст `3`, и генератор завершится. Поэтому остаток равен `[2, 3]`. Уже отданное значение `1` повторно не возвращается, и обход не «обнуляется».
Подробный разбор →В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.
Тренировать Python в Telegram