Ключевое слово yield в Python превращает обычную функцию в функцию-генератор. Такая функция не считает весь результат сразу: она отдаёт одно значение, замирает на строке с yield и продолжает с этого же места, когда у неё попросят следующее. Поэтому генератор хранит в памяти только текущий элемент. Он подходит для больших файлов, потоков данных и бесконечных последовательностей.
Обычные функции с return мы разбирали в статье про функции в Python. Здесь только про yield: как он работает, чем отличается от return, где пригодится и какие ошибки с ним делают чаще всего.
SkillboxПрофессия «Python-разработчик»157 335 ₽10 месяцев
НетологияFullstack-разработчик на Python137 000 ₽21 месяц
GeekBrainsДО Профессия Python-разработчик149 001 ₽10 месяцев
SkillFactoryПрофессия Python-разработчик140 040 ₽10 месяцев
Академия СинергияPython-разработчик + ИИ77 900 ₽6 месяцев
ЭдюсонPython-разработчик89 919 ₽9 месяцев
Как работает yield: пример с next()

Возьмём функцию обратного отсчёта. Вместо того чтобы собрать числа в список и вернуть его через return, она отдаёт их по одному через yield.
def countdown(n):
print("Старт")
while n > 0:
yield n
n -= 1
print("Финиш")
gen = countdown(3)
print(type(gen))
print(next(gen))
print(next(gen))
print(next(gen))
next(gen)
Вывод:
<class 'generator'>
Старт
3
2
1
Финиш
Traceback (most recent call last):
...
StopIteration
Что здесь произошло по шагам:
- Вызов
countdown(3)не напечатал «Старт». Python увиделyieldв теле и вернул объект-генератор, код функции пока не выполнялся. - Первый
next(gen)запустил тело: напечатал «Старт», дошёл доyield n, отдал 3 и остановился. - Второй и третий
next()продолжили с того же места. Переменнаяnпомнила своё значение, циклwhileсделал ещё два шага. - Четвёртый
next()вывел цикл из работы, функция напечатала «Финиш» и закончилась. Значений больше нет, поэтому Python выбросилStopIteration.
Так это описано и в глоссарии документации Python: каждое выражение yield временно приостанавливает обработку кода и сохраняет состояние выполнения, включая локальные переменные. Обычная функция при каждом вызове начинает сначала, генератор продолжает с места остановки.
Вручную next() зовут редко. Обычно генератор перебирают циклом for, который сам вызывает next() и сам ловит StopIteration:
def countdown(n):
while n > 0:
yield n
n -= 1
for x in countdown(3):
print(x)
print(list(countdown(5)))
3
2
1
[5, 4, 3, 2, 1]
Как устроен сам цикл с условием, подробно показали в разборе цикла while в Python.
Чем yield отличается от return
Частый вопрос на собеседованиях звучит так: каким ключевым словом заменить return, чтобы функция стала генератором? Ответ: yield. Но разница между ними больше, чем одно слово.
| return | yield | |
|---|---|---|
| Что возвращает вызов функции | готовый результат | объект-генератор |
| Сколько раз срабатывает | один раз, функция завершается | сколько угодно, функция замирает и продолжает |
| Локальные переменные | теряются после выхода | сохраняются между вызовами |
| Память | весь результат целиком | одно текущее значение |
| Повторный обход | список можно читать сколько угодно раз | генератор проходится один раз |
Разницу в памяти легко проверить. Две функции считают квадраты миллиона чисел: первая складывает их в список, вторая отдаёт по одному.
import sys
def squares_list(n):
result = []
for i in range(n):
result.append(i * i)
return result
def squares_gen(n):
for i in range(n):
yield i * i
print(sys.getsizeof(squares_list(1_000_000)))
print(sys.getsizeof(squares_gen(1_000_000)))
print(sum(squares_list(1_000_000)) == sum(squares_gen(1_000_000)))
8448728
208
True
Мы запускали код в Python 3.14 на macOS, на другой версии и системе байты могут немного отличаться. Список занял 8 448 728 байт, генератор 208 байт, сумма у обоих одна и та же. Генератор не хранит квадраты, он считает каждый в тот момент, когда sum() его запросил. Такой подход называют ленивыми вычислениями.
Причём реальный разрыв ещё больше. По документации sys.getsizeof() функция учитывает только память самого объекта, а не объектов, на которые он ссылается. Для списка это место под миллион ссылок, сами числа лежат отдельно и в эти 8 МБ не вошли. Как устроены списки и почему они так растут, разбирали в материале о списках в Python.
Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединитьсяПравило выбора простое: если результат нужен целиком, его будут перечитывать или брать по индексу, верните список через
return. Если значения нужны по очереди и один раз, пишите генератор сyield.
Генератор и итератор: что yield делает за вас

Любой генератор относится к итераторам, то есть к объектам, которые отдают значения по одному через next(). Итератор можно написать и без yield, классом с методами __iter__() и __next__(). Вот тот же обратный отсчёт двумя способами:
class Countdown:
def __init__(self, n):
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.n <= 0:
raise StopIteration
value = self.n
self.n -= 1
return value
def countdown(n):
while n > 0:
yield n
n -= 1
print(list(Countdown(3)))
print(list(countdown(3)))
gen = countdown(3)
print(iter(gen) is gen)
print(hasattr(gen, "__next__"))
[3, 2, 1]
[3, 2, 1]
True
True
Результат одинаковый, но класс занял 12 строк, а функция 4. В классе вы сами храните состояние в self.n и сами бросаете StopIteration. В генераторе состояние живёт в локальных переменных, а StopIteration Python выбрасывает сам, когда функция заканчивается. Последние две строки вывода показывают, что у генератора есть оба метода итератора.
Классы и методы вроде __init__ подробно разобрали в статье про ООП в Python. Итератор-класс пишут, когда у объекта кроме перебора есть другие методы и данные. Для простой последовательности хватает yield.
Где yield полезен на практике
Генераторы нужны там, где данных много или их поток не кончается. Три типовых случая.
Чтение большого файла по строкам
Лог на несколько гигабайт целиком в память не загрузить. Генератор читает файл строка за строкой, а второй генератор отбирает только нужные. Получается конвейер: каждая строка проходит его целиком, и только потом читается следующая.
with open("app.log", "w", encoding="utf-8") as f:
f.write("INFO старт\nERROR нет связи с базой\nINFO повтор\nERROR таймаут\n")
def read_lines(path):
with open(path, encoding="utf-8") as f:
for line in f:
yield line.rstrip("\n")
def only_errors(lines):
for line in lines:
if line.startswith("ERROR"):
yield line
for line in only_errors(read_lines("app.log")):
print(line)
ERROR нет связи с базой
ERROR таймаут
Первые две строки только создают тестовый файл, в работе у вас будет свой. Как открывать файлы и зачем нужен with, рассказали в инструкции как открыть файл в Python. Тот же приём работает с таблицами: построчное чтение CSV-файлов через модуль csv тоже ленивое.
Бесконечная последовательность
Список бесконечной длины создать нельзя, а генератор можно: он посчитает ровно столько значений, сколько у него попросят. Вот числа Фибоначчи:
from itertools import islice
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
print(list(islice(fibonacci(), 10)))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Цикл while True внутри генератора не зависает: после каждого yield функция ждёт, пока её позовут снова. Функция islice() берёт первые десять значений и останавливается. Без неё list(fibonacci()) съел бы всю память, поэтому бесконечный генератор всегда перебирают с ограничением или с break внутри цикла.
Генераторное выражение вместо функции
Для простых случаев функцию писать не обязательно. Генераторное выражение выглядит как списковое, только в круглых скобках:
squares = (i * i for i in range(5))
print(squares)
print(sum(squares))
print(sum(squares))
<generator object <genexpr> at 0x107465cb0>
30
0
Адрес в первой строке у вас будет другим. Важнее третья строка: второй sum() вернул 0, потому что генератор уже отдал все значения. Это одна из ошибок, о которых ниже.
yield from: как передать работу другому генератору
Конструкция yield from появилась в Python 3.3 по PEP 380. Она отдаёт наружу все значения другого генератора или любого перебираемого объекта, и не нужно писать для этого отдельный цикл for с yield внутри.
Пример: развернуть вложенный список любой глубины в плоский.
def flatten(items):
for item in items:
if isinstance(item, list):
yield from flatten(item)
else:
yield item
print(list(flatten([1, [2, 3], [4, [5, 6]]])))
[1, 2, 3, 4, 5, 6]
Функция вызывает сама себя для каждого вложенного списка. Про такие вызовы подробнее в статье о рекурсии в Python.
У yield from есть и вторая работа: он забирает значение, которое генератор вернул через return. В генераторе return тоже разрешён, но его значение не попадает в перебор:
def numbers():
yield 1
yield 2
return "готово"
def wrapper():
result = yield from numbers()
print("Генератор вернул:", result)
print(list(wrapper()))
Генератор вернул: готово
[1, 2]
В список попали только 1 и 2. Строка «готово» досталась переменной result: по справочнику языка, когда вложенный генератор заканчивается, его возвращённое значение становится значением выражения yield from.
send(): как передать значение внутрь генератора
Генератор умеет не только отдавать значения, но и принимать их. Метод send() добавлен в Python 2.5 по PEP 342. По справочнику языка, если генератор продолжили через send(), выражение yield получает переданное значение, а при обычном next() оно равно None.
Так можно считать скользящее среднее, не храня все числа:
def averager():
total = 0
count = 0
average = None
while True:
value = yield average
total += value
count += 1
average = total / count
avg = averager()
next(avg)
print(avg.send(10))
print(avg.send(20))
print(avg.send(60))
10.0
15.0
30.0
Строка next(avg) перед первым send() обязательна. Она доводит генератор до первого yield, где тот и ждёт значения. Без неё Python не даст отправить число:
TypeError: can't send non-None value to a just-started generator
В прикладном коде send() встречается нечасто, но без него трудно понять, как в Python устроены сопрограммы.
Частые ошибки с yield

Большинство проблем с генераторами сводится к пяти ситуациям.
Генератор пуст при втором обходе
gen = countdown(3)
print(list(gen))
print(list(gen))
[3, 2, 1]
[]
Причина: генератор одноразовый, после первого прохода он исчерпан.
Решение: вызовите функцию заново, list(countdown(3)), или сохраните результат в список, если он нужен несколько раз.
Функцию вызвали, а она ничего не делает
def greet():
print("Привет!")
yield
greet()
print("Ничего не напечаталось")
next(greet())
Ничего не напечаталось
Привет!
Причина: вызов функции с yield только создаёт генератор. Тело выполнится, когда генератор начнут перебирать.
Решение: переберите результат через for, list() или next(). Если перебирать нечего и функция просто что-то делает, yield в ней не нужен.
TypeError: object of type ‘generator’ has no len()
Причина: генератор не знает, сколько значений отдаст, пока их не посчитает, поэтому у него нет длины и индексов.
Решение: если длина нужна, превратите генератор в список: len(list(gen)). Помните, что после этого генератор исчерпан.
RuntimeError: generator raised StopIteration
def broken():
yield 1
raise StopIteration
print(list(broken()))
RuntimeError: generator raised StopIteration
Причина: по PEP 479 с Python 3.7 исключение StopIteration, выброшенное внутри генератора, заменяется на RuntimeError. Старые примеры из интернета так завершали генератор, и теперь они падают.
Решение: чтобы закончить генератор, используйте return.
SyntaxError: ‘yield’ inside list comprehension
result = [(yield x) for x in range(3)]
Причина: с Python 3.8 yield запрещён внутри списковых включений и генераторных выражений, это записано в справочнике языка.
Решение: если нужен генератор, напишите генераторное выражение без yield, (x for x in range(3)), или обычную функцию с циклом.
Где освоить Python системно
Генераторы закрывают одну тему из многих. Чтобы уверенно писать программы, нужны функции, классы, работа с файлами и исключениями, тесты и практика на задачах с проверкой. Начать можно с подборки задач по Python для начинающих, а системно этому учат курсы с наставником. Ниже онлайн-курсы по Python из нашего каталога, все варианты собраны на странице курсов Python.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Профессия «Python-разработчик» Перейти на сайт курса | 157 335 ₽ | 5987 ₽/мес. | 10 месяцев | Обзор курса | |
| Fullstack-разработчик на Python Перейти на сайт курса | 137 000 ₽ | 7125 ₽/мес. | 21 месяц | Обзор курса | |
| ДО Профессия Python-разработчик Перейти на сайт курса | 149 001 ₽ | 3167 ₽/мес. | 10 месяцев | Обзор курса | |
| Автоматизированное тестирование на Python Перейти на сайт курса | 122 226 ₽ | 4108 ₽/мес. | 9 месяцев | Обзор курса | |
| Профессия Python-разработчик Перейти на сайт курса | 140 040 ₽ | 3890 ₽/мес. | 10 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по Python




