В чем разница между yield и return в Python и когда использовать yield?

«В чем разница между yield и return в Python и когда использовать yield?» — вопрос из категории Python, который задают на 26% собеседований Data Scientist / ML Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

return завершает выполнение функции и возвращает одно значение. yield превращает функцию в генератор, который может возвращать последовательность значений по одному, сохраняя состояние между вызовами.

Ключевые отличия:

Аспект return yield
Возврат Одно значение Последовательность значений
Состояние Функция завершается Функция приостанавливается
Память Все данные сразу Ленивая загрузка
Тип Любой объект generator object

Пример генератора с yield:

def read_large_file(file_path):
    """Чтение огромного файла построчно без загрузки в память"""
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            yield line.strip()

# Использование
for line in read_large_file('huge_data.txt'):
    process_line(line)  # Обрабатываем по одной строке

Когда использовать yield:

  1. Работа с большими данными:

    def batch_generator(data, batch_size=1000):
    """Разбивает данные на батчи"""
    for i in range(0, len(data), batch_size):
        yield data[i:i + batch_size]
  2. Бесконечные последовательности:

    
    def fibonacci_generator():
    """Генератор чисел Фибоначчи"""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci_generator() print(next(fib)) # 0 print(next(fib)) # 1 print(next(fib)) # 1 print(next(fib)) # 2


3. **Конвейерная обработка (pipeline):**
```python
def parse_logs(log_lines):
    for line in log_lines:
        if 'ERROR' in line:
            yield line

def extract_timestamps(error_lines):
    for line in error_lines:
        timestamp = line.split()[0]
        yield timestamp

# Композиция генераторов
logs = read_large_file('app.log')
errors = parse_logs(logs)
timestamps = extract_timestamps(errors)

for ts in timestamps:
    print(f"Ошибка в {ts}")
  1. Сохранение состояния между вызовами:
    
    def stateful_counter(start=0):
    count = start
    while True:
        increment = yield count
        if increment is not None:
            count += increment
        else:
            count += 1

counter = stateful_counter(10) next(counter) # Инициализация print(counter.send(5)) # 15 (10 + 5) print(next(counter)) # 16 print(counter.send(-3)) # 13


**Под капотом:** При использовании `yield` Python создает объект-генератор с методами `__next__()` и `send()`. Фрейм функции сохраняется в heap, а не в stack, что позволяет возобновлять выполнение.

**Синтаксический сахар: генераторные выражения**
```python
# Эквивалент генератора в одну строку
squares = (x**2 for x in range(1000000))  # Не выделяет память
sum_of_squares = sum(squares)  # Вычисляет на лету

Вывод: Используйте yield когда нужно:

  • Обрабатывать данные, которые не помещаются в память
  • Создавать ленивые вычисления
  • Реализовывать конвейеры обработки
  • Сохранять состояние между вызовами

Используйте return для обычных функций, возвращающих готовый результат.