Yield: примеры (PYTHON)

Практическое применение yield в Python на примерах
Раздел: Операторы, Генераторы
yield(value: Any)

Описание конструкции yield

Yield - это ключевое слово в Python, используемое для создания генераторов. Оно применяется в теле функции, превращая её в функцию-генератор. Генератор - это специальный тип итератора, который генерирует значения последовательно и по требованию, что позволяет экономить память при работе с большими наборами данных.

Когда функция содержит yield, её вызов возвращает объект-генератор, не выполняя код функции сразу. Код выполняется при каждом вызове next() или в цикле for, пока не встретится следующее yield. Выполнение приостанавливается, и значение после yield возвращается как элемент итерации. При следующем вызове выполнение продолжается с этого места.

У yield есть несколько особенностей:

  • Оно может возвращать значение: yield выражение. Если выражение не указано, возвращается None.
  • Через метод generator.send(value) можно передать значение в генератор, которое будет получено в месте приостановки yield. Синтаксис: переменная = yield результат.
  • Метод generator.throw(тип_ошибки) позволяет инициировать исключение внутри генератора.
  • Метод generator.close() останавливает генератор, вызывая исключение GeneratorExit.
  • Генератор завершает работу при достижении конца функции или инструкции return. В Python 3.3+ return может возвращать значение, доступное через атрибут value исключения StopIteration.

Краткие примеры использования

Пример 1: Простой генератор последовательности чисел.

def count_up_to(n):
    i = 1
    while i <= n:
        yield i
        i += 1

for num in count_up_to(3):
    print(num)
1
2
3

Пример 2: Генератор с использованием send() для получения данных извне.

def echo():
    print("Старт")
    while True:
        value = yield
        print(f"Получено: {value}")

gen = echo()
next(gen)          # Инициализация генератора, вывод "Старт"
gen.send("Привет") # Получение значения
Старт
Получено: Привет

Пример 3: Генератор с throw() для вызова исключения.

def resilient_gen():
    try:
        yield "Работаю"
    except ValueError:
        yield "Обработана ошибка"

gen = resilient_gen()
print(next(gen))
print(gen.throw(ValueError))
Работаю
Обработана ошибка

Пример 4: Использование close() для остановки генератора.

def infinite():
    try:
        while True:
            yield "данные"
    except GeneratorExit:
        print("Генератор завершен")

gen = infinite()
print(next(gen))
print(next(gen))
gen.close()
данные
данные
Генератор завершен

Похожие конструкции в Python

Обычные функции с return: Возвращают одно значение и завершают выполнение. Подходят для вычислений с конечным результатом, а не для последовательностей.

Итераторы на основе классов: Реализация интерфейса __iter__ и __next__. Требуют больше кода, но обеспечивают полный контроль. Используются для сложных сценариев итерации.

Генераторные выражения: Синтаксис (выражение for элемент in итерируемый_объект). Создают анонимные генераторы для простых преобразований, например, (x*2 for x in range(5)). Более лаконичны, но менее гибки, чем функции с yield.

Встроенные функции, возвращающие итераторы: map, filter, zip. Предлагают функциональный стиль для обработки данных без явного создания генераторов.

Предпочтения: yield оптимален для генерации последовательностей с состоянием или сложной логикой. Генераторные выражения удобны для простых преобразований в одну строку. Итераторы на классах актуальны при необходимости интеграции с другими компонентами ООП.

Аналоги в других языках программирования

PHP: Использует yield для генераторов с версии 5.5. Похож на Python, но имеет дополнительные методы Generator::send и Generator::throw. Пример:

function countUpTo($n) {
    for ($i = 1; $i <= $n; $i++) {
        yield $i;
    }
}

foreach (countUpTo(3) as $num) {
    echo $num . "\n";
}
1
2
3

JavaScript: Генераторы введены в ES6 через function* и yield. Поддерживают next(), send() и throw(). Пример:

function* countUpTo(n) {
    for (let i = 1; i <= n; i++) {
        yield i;
    }
}

for (let num of countUpTo(3)) {
    console.log(num);
}
1
2
3

C#: Использует yield return и yield break в методах, возвращающих IEnumerable или IEnumerator. Не поддерживает передачу значений в генератор. Пример:

using System;
using System.Collections.Generic;

public class Program {
    public static IEnumerable<int> CountUpTo(int n) {
        for (int i = 1; i <= n; i++) {
            yield return i;
        }
    }

    public static void Main() {
        foreach (var num in CountUpTo(3)) {
            Console.WriteLine(num);
        }
    }
}
1
2
3

Golang: Не имеет прямого аналога yield. Похожие паттерны реализуются через горутины и каналы для асинхронной генерации данных.

Kotlin: Предоставляет конструкцию sequence { yield(value) } в стандартной библиотеке. Пример:

import kotlin.sequences.*

fun countUpTo(n: Int) = sequence {
    for (i in 1..n) {
        yield(i)
    }
}

fun main() {
    countUpTo(3).forEach { println(it) }
}
1
2
3

Lua: Использует корутины с функциями coroutine.create и coroutine.yield, что позволяет приостанавливать выполнение и обмениваться данными.

Типичные ошибки при использовании

Ошибка 1: Использование yield вне функции. Это приводит к синтаксической ошибке.

yield 5  # Выполнено в глобальной области видимости
SyntaxError: 'yield' outside function

Ошибка 2: Попытка использовать return со значением в генераторе в Python 2, где это не поддерживается. В Python 2 генераторы не могут возвращать значения через return.

def gen():
    yield 1
    return "конец"  # В Python 2 вызовет SyntaxError
SyntaxError: 'return' with argument inside generator

Ошибка 3: Неинициализация генератора перед вызовом send(). Метод send() требует предварительного вызова next() для перехода к первому yield.

def gen():
    value = yield
    print(value)

g = gen()
g.send("данные")  # Ошибка
TypeError: can't send non-None value to a just-started generator

Ошибка 4: Обращение к генератору как к списку, например, попытка получить элемент по индексу. Генераторы не поддерживают индексацию.

def gen():
    yield 1
    yield 2

g = gen()
print(g[0])
TypeError: 'generator' object is not subscriptable

Изменения в последних версиях Python

Python 3.3: Введена конструкция yield from для делегирования генераторов. Она упрощает создание субгенераторов и позволяет возвращать значение из return в генераторе через атрибут value исключения StopIteration.

Python 3.6: Добавлены асинхронные генераторы через async def и await yield. Они используются в асинхронном коде для генерации последовательностей с поддержкой async for.

Python 3.7: Улучшена производительность асинхронных генераторов и исправлены некоторые ошибки, связанные с их завершением.

Python 3.8: Не было значительных изменений для yield, но введен синтаксис := (моржовый оператор), который может использоваться вместе с генераторами для упрощения кода.

Расширенные примеры применения

Пример 1: Использование yield from для объединения генераторов.

Пример python
def sub_gen():
    yield "a"
    yield "b"

def main_gen():
    yield 1
    yield from sub_gen()
    yield 2

for item in main_gen():
    print(item)
1
a
b
2

Пример 2: Асинхронный генератор для работы с асинхронными данными.

Пример python
import asyncio

async def async_counter(n):
    for i in range(n):
        await asyncio.sleep(0.1)  # Имитация асинхронной операции
        yield i

async def main():
    async for num in async_counter(3):
        print(num)

asyncio.run(main())
0
1
2

Пример 3: Генератор для чтения большого файла по строкам с экономией памяти.

Пример python
def read_large_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            yield line.strip()

# Предположим, что файл 'data.txt' содержит строки 'A', 'B', 'C'
for line in read_large_file('data.txt'):
    print(line)
A
B
C

Пример 4: Генератор как корутина для двустороннего обмена данными.

Пример python
def processor():
    total = 0
    while True:
        value = yield total
        if value is None:
            break
        total += value

gen = processor()
next(gen)                # Инициализация
print(gen.send(10))      # Отправка 10, получение 10
print(gen.send(5))       # Отправка 5, получение 15
print(gen.send(None))    # Завершение
10
15
Traceback (most recent call last):
  File "", line 13, in 
StopIteration

Пример 5: Создание бесконечной последовательности чисел Фибоначчи.

Пример python
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
for _ in range(6):
    print(next(fib))
0
1
1
2
3
5

питон yield function comments

En
Yield Produce a value from a generator