**Что делает генератор особенным?**
Обычная функция вычисляет и возвращает все результаты сразу — вызывающий ждёт пока весь список строится в памяти. Функция-генератор использует `yield` для возврата значений *по одному*, приостанавливая выполнение между ними. Вызывающий получает значение, делает с ним что-то, затем запрашивает следующее:
```python
def count_up(n):
i = 0
while i < n:
yield i # пауза, передаём значение вызывающему
i += 1 # возобновление при следующем next()
gen = count_up(3) # создаёт объект-генератор, ничего не выполняет
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # StopIteration
```
Ключевая мысль: вызов `count_up(3)` *не* выполняет тело функции. Он создаёт объект-генератор запоминающий где возобновиться. Выполнение начинается только при первом вызове `next()`.
**Генератор как машина состояний**
Генератор — приостановленная функция. Python сохраняет весь её локальный scope — локальные переменные, указатель инструкции — между вызовами `yield`. Генератор может хранить состояние без класса:
```python
def running_total(numbers):
total = 0
for n in numbers:
total += n
yield total # total сохраняется между yield
list(running_total([1, 2, 3, 4])) # [1, 3, 6, 10]
```
**Преимущество по памяти**
Генератор производит одно значение за раз и сразу отбрасывает его. Использует O(1) памяти независимо от длины последовательности:
```python
import sys
big_list = [x * x for x in range(1_000_000)] # ~8 МБ
big_gen = (x * x for x in range(1_000_000)) # ~200 байт
print(sys.getsizeof(big_list)) # 8448728
print(sys.getsizeof(big_gen)) # 208
```
Генераторные выражения используют `()` вместо `[]`. Работают везде где ожидается итерируемый объект — `sum()`, `max()`, `join()`, циклы `for`:
```python
total = sum(x * x for x in range(1_000_000)) # [] не нужны внутри sum()
```
**Когда НЕ стоит использовать генераторы**
Генераторы исчерпываются после одного прохода — нельзя итерировать повторно или обращаться по индексу. Если нужно итерировать последовательность несколько раз — материализуйте её: `result = list(my_gen())`. Генераторы не поддерживают `len()`.
yield from, send(), throw(), close() и бесконечные генераторы
**`yield from` — делегирование суб-генераторам**
`yield from iterable` возвращает каждый элемент из другого итерируемого без ручного цикла. Он также корректно передаёт `send()`, `throw()` и `StopIteration` через цепочку делегирования — то чего ручной цикл `for` сделать не может:
```python
def flatten(nested):
for item in nested:
if isinstance(item, list):
yield from flatten(item) # делегируем рекурсивно
else:
yield item
print(list(flatten([1, [2, [3, 4]], 5]))) # [1, 2, 3, 4, 5]
```
**`.send()` — двустороннее общение**
Генератор также может *получать* значения. `.send(value)` возобновляет генератор и заставляет `yield` вычислиться как отправленное значение. Первый вызов должен быть `.send(None)` или `next()` для продвижения к первому `yield`:
```python
def accumulator():
total = 0
while True:
value = yield total # yield отправляет total наружу, получает следующее значение
if value is None:
break
total += value
acc = accumulator()
next(acc) # продвигаемся к первому yield — возвращает 0
acc.send(10) # total = 10, возвращает 10
acc.send(5) # total = 15, возвращает 15
acc.send(None) # выходит из цикла, StopIteration
```
**`.throw()` и `.close()`**
`.throw(exc)` вбрасывает исключение в точке текущего `yield`. `.close()` вбрасывает `GeneratorExit`, давая генератору возможность очиститься в блоке `try/finally`:
```python
def managed_gen():
try:
while True:
yield
finally:
print('generator cleaned up') # всегда выполняется при .close()
g = managed_gen()
next(g)
g.close() # generator cleaned up
```
**Бесконечные генераторы**
Генераторы прекрасно подходят когда последовательность не имеет естественного конца. Поскольку значения ленивые, бесконечный генератор совершенно безопасен пока вы контролируете сколько элементов берёте:
```python
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
import itertools
print(list(itertools.islice(fibonacci(), 8))) # [0, 1, 1, 2, 3, 5, 8, 13]
```
itertools, конвейерные паттерны и когда выбрать генератор
**itertools — инструментарий генераторов**
Модуль `itertools` предоставляет ленивые строительные блоки. Все функции возвращают итераторы, а не списки. Они естественно компонуются для построения конвейеров данных:
| Функция | Что делает | Пример |
|---|---|---|
| `chain(a, b, ...)` | Итерирует последовательности одну за другой | `chain('AB', 'CD')` → A B C D |
| `islice(it, n)` | Берёт первые n элементов (ленивая нарезка) | `islice(fib(), 5)` → первые 5 Фибоначчи |
| `takewhile(pred, it)` | Выдаёт пока предикат True | `takewhile(lambda x: x<5, count())` |
| `dropwhile(pred, it)` | Пропускает пока True, затем выдаёт остальное | `dropwhile(lambda x: x<3, [1,2,3,4])` → 3 4 |
| `groupby(it, key)` | Группирует *последовательные* элементы с одинаковым ключом | сначала сортировать |
| `product(a, b)` | Декартово произведение | `product('AB', [1,2])` → A1 A2 B1 B2 |
| `combinations(it, r)` | Комбинации длины r, без повторений | `combinations('ABC', 2)` → AB AC BC |
| `accumulate(it, fn)` | Накопительная редукция | `accumulate([1,2,3,4])` → 1 3 6 10 |
**Построение конвейеров**
```python
import itertools
# Первые 5 чётных квадратов больше 50
squares = (x * x for x in itertools.count(1))
above_50 = itertools.dropwhile(lambda x: x <= 50, squares)
even_above_50 = (x for x in above_50 if x % 2 == 0)
result = list(itertools.islice(even_above_50, 5))
print(result) # [64, 100, 196, 256, 400]
# Группировка слов по первой букве (сначала нужно отсортировать!)
words = ['apple', 'avocado', 'banana', 'blueberry', 'cherry']
for letter, group in itertools.groupby(words, key=lambda w: w[0]):
print(letter, list(group))
```
**Когда использовать генератор, а когда список**
Генератор — когда итерируете один раз, последовательность большая или бесконечная, строите конвейер где каждый шаг фильтрует или трансформирует.
Список — когда нужно итерировать несколько раз, нужен `len()`, индексация или нарезка, или последовательность мала и хотите видеть все значения сразу.
Напишите функцию-генератор `fibonacci()`, которая бесконечно выдаёт числа Фибоначчи (0, 1, 1, 2, 3, 5, 8, ...). Используйте `itertools.islice`, чтобы получить первые `n` значений.
import itertools
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
first10 = list(itertools.islice(fibonacci(), 10))
print(first10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Напишите функцию-генератор `frange(start, stop, step)`, которая выдаёт вещественные числа от `start` до `stop` (не включая) с шагом `step`. Она должна работать как `range()`, но поддерживать дробный шаг.
Напишите генератор `flatten(nested)`, который рекурсивно разглаживает вложенный список любой глубины и выдаёт отдельные элементы. Используйте `yield from` для рекурсии. Не-списки выдаются как есть.
Напишите генератор-корутину `running_average()`, вычисляющую скользящее среднее. Каждый вызов `.send(value)` должен добавлять `value` к сумме и `yield`-нуть текущее среднее. Запустите генератор через `next()` перед отправкой значений.
def running_average():
total = 0
count = 0
value = yield # точка запуска — первый next() останавливается здесь
while True:
total += value
count += 1
value = yield total / count
avg = running_average()
next(avg) # продвигаемся до первого yield
print(avg.send(10)) # 10.0
print(avg.send(20)) # 15.0
print(avg.send(30)) # 20.0
Напишите генератор `sliding_window(iterable, n)`, который выдаёт кортежи из `n` последовательных элементов, сдвигаясь на один шаг. Например, `sliding_window([1,2,3,4,5], 3)` выдаёт `(1,2,3)`, `(2,3,4)`, `(3,4,5)`.
Напишите две функции-генератора:
- `take(n, iterable)` — выдаёт первые `n` элементов из iterable.
- `drop(n, iterable)` — пропускает первые `n` элементов и выдаёт остальные.
Обе должны работать лениво (не конвертировать в список внутри).
def take(n, iterable):
for i, item in enumerate(iterable):
if i >= n:
break
yield item
def drop(n, iterable):
for i, item in enumerate(iterable):
if i >= n:
yield item
nums = range(10)
print(list(take(3, nums))) # [0, 1, 2]
print(list(drop(7, nums))) # [7, 8, 9]
# Или через itertools:
import itertools
list(itertools.islice(nums, 3)) # take(3)
list(itertools.islice(nums, 7, None)) # drop(7)
Напишите генератор `chunks(iterable, n)`, который разбивает iterable на последовательные фрагменты размером `n`. Последний фрагмент может быть меньше, если длина iterable не делится на `n`. Выдавайте каждый фрагмент как список.
Напишите генератор `primes()`, который бесконечно выдаёт простые числа с помощью простого пробного деления: для каждого кандидата проверяйте делимость только на ранее найденные простые числа. Используйте `itertools.islice` для первых 10 простых.
С помощью `itertools.chain`, `itertools.filterfalse` и генераторного выражения постройте пайплайн, который:
1. Объединяет два списка: `[1, 2, 3]` и `[4, 5, 6]`.
2. Фильтрует чётные числа.
3. Возводит оставшиеся числа в квадрат.
Выведите результат как список.
import itertools
a = [1, 2, 3]
b = [4, 5, 6]
# Постройте пайплайн с itertools.chain, itertools.filterfalse
# и генераторным выражением для возведения в квадрат.
result = []
print(result) # [1, 9, 25]
Решение
import itertools
a = [1, 2, 3]
b = [4, 5, 6]
chained = itertools.chain(a, b) # 1 2 3 4 5 6
odds = itertools.filterfalse(lambda x: x % 2 == 0, chained) # 1 3 5
result = list(x * x for x in odds) # 1 9 25
print(result) # [1, 9, 25]
10
Группировка слов по первой букве через itertools.groupby
Имея отсортированный список слов, используйте `itertools.groupby`, чтобы сгруппировать их по первой букве и вывести каждую группу на отдельной строке: `'a: apple, avocado'`. Список уже отсортирован по алфавиту.
import itertools
words = ['apple', 'avocado', 'banana', 'blueberry', 'cherry', 'coconut']
# Используйте itertools.groupby для группировки по первой букве
# Ожидаемый вывод:
# a: apple, avocado
# b: banana, blueberry
# c: cherry, coconut
Решение
import itertools
words = ['apple', 'avocado', 'banana', 'blueberry', 'cherry', 'coconut']
for letter, group in itertools.groupby(words, key=lambda w: w[0]):
print(f'{letter}: {", ".join(group)}')
No split tab
Настройки cookies
Мы используем необходимые cookies для работы сайта. С вашего разрешения мы также можем сохранять настройки сайта и использовать аналитические и рекламные cookies, чтобы понимать использование сайта и поддерживать развитие проекта.
* Вы всегда можете изменить свой выбор в настройках сайта.
Выберите категории cookies
Настройки аналитики
Можно отключить аналитику использования платформы. Также можно отправить в Google Analytics запрос на удаление данных об использовании этого сайта, связанных с этим браузером.
Учебный workspace
Учитесь, читая, запуская код и решая задачи.
Практикуйте программирование с пояснениями тем, упражнениями, инструментами browser IDE, проверкой regex и тренировкой печати кода в одном workspace.
Открывайте инструменты во вкладках.Упражнения, IDE-инструменты и тренажеры остаются доступными как вкладки сайта.
Переключайтесь без потери контекста.Переходите между пояснениями, кодом и утилитами, сохраняя свое место.
Используйте sidebar как карту.Левые панели содержат навигацию, настройки, файлы, libraries и управление инструментами.
PythonJavaScriptSQLite
Одна IDE, три практичных режима
Python в браузере.Запускайте небольшие скрипты, пробуйте библиотеки и тренируйте API-запросы без установки.
JavaScript для быстрых экспериментов.Проверяйте код для браузера и сравнивайте идеи рядом с учебными материалами.
SQLite для практики с данными.Открывайте обозреватель базы данных, изучайте таблицы, пишите запросы и учитесь SQL локально.
ТемаIDE
Работайте рядом в split tabs
Держите инструкции перед глазами.Откройте упражнение или справочную страницу рядом с IDE, вместо постоянных переключений.
Сравнивайте инструменты во время обучения.Размещайте проверки regex, пояснения и эксперименты с кодом рядом, когда это нужно для задачи.
Закройте split, когда закончите.Workspace вернётся к одной сфокусированной вкладке, а открытые вкладки сайта останутся доступны.
Тренажер слепой печати кода
Или просто текста
Тренажер рассчитан на физическую клавиатуру.Откройте этот раздел на ноутбуке или компьютере с широким экраном. На телефоне тренировка слепой печати не будет корректной.
Скорость: 0 зн/мин
0 слов/мин
Лучшая скорость (60с): 0 зн/мин
0 слов/мин
Ошибки: 0
Общее время: 0.0 с
Для активации режима слепого набора не подсматривайте на физическую клавиатуру.