← Назад к вопросам

Что такое yield в Python?

1.6 Junior🔥 231 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое yield в Python?

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

Основной принцип: ленивые вычисления

Обычная функция вычисляет все результаты сразу:

def numbers():
    return [1, 2, 3, 4, 5]  # создаёт весь список в памяти

for n in numbers():
    print(n)

Генератор с yield вычисляет результаты по одному:

def numbers():
    yield 1  # сохраняет состояние и возвращает 1
    yield 2  # продолжает отсюда, возвращает 2
    yield 3
    yield 4
    yield 5

for n in numbers():  # вызывает функцию, получает по одному значению
    print(n)

1. Экономия памяти

Проблема: создание большого списка

def get_large_list(n):
    """Обычная функция — весь список в памяти"""
    result = []
    for i in range(n):
        result.append(i ** 2)
    return result

big_list = get_large_list(1000000)  # создаёт 1 млн элементов в памяти

Решение: генератор

def get_large_numbers(n):
    """Генератор — по одному числу в памяти"""
    for i in range(n):
        yield i ** 2

for num in get_large_numbers(1000000):  # каждый раз один элемент в памяти
    print(num)

2. Как работает yield: пошагово

def simple_generator():
    print("Начало")
    yield 1          # первый вызов next() возвращает 1
    print("После 1")
    yield 2          # второй вызов next() возвращает 2
    print("После 2")
    yield 3          # третий вызов next() возвращает 3
    print("Конец")

gen = simple_generator()  # функция НЕ выполнялась!
print(next(gen))         # Начало, потом возвращает 1
print(next(gen))         # После 1, потом возвращает 2
print(next(gen))         # После 2, потом возвращает 3
print(next(gen))         # Конец, затем StopIteration

3. Генератор as итератор

def count_up(max):
    current = 1
    while current <= max:
        yield current
        current += 1

# Способ 1: for loop
for n in count_up(3):
    print(n)  # 1, 2, 3

# Способ 2: next()
gen = count_up(3)
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3
# print(next(gen))  # StopIteration

# Способ 3: list() — материализовать в список
gen = count_up(3)
nums = list(gen)  # [1, 2, 3]

4. Бесконечные последовательности

Генератор идеален для бесконечных потоков данных:

def fibonacci():
    """Бесконечный генератор чисел Фибоначчи"""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Получить первые 10 чисел Фибоначчи
fib_gen = fibonacci()
fib_list = [next(fib_gen) for _ in range(10)]
print(fib_list)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# Или с itertools.islice
from itertools import islice
fib_list = list(islice(fibonacci(), 10))
print(fib_list)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

5. Чтение больших файлов

def read_large_file(filepath, chunk_size=1024):
    """Читает большой файл частями"""
    with open(filepath, 'r') as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            yield chunk

# Обработка гигабайтного файла без загрузки в памяти
for chunk in read_large_file('huge_file.txt'):
    print(f"Обработана часть размером {len(chunk)} символов")

# Более практично — читать строки
def read_lines(filepath):
    """Читает файл построчно"""
    with open(filepath, 'r') as f:
        for line in f:  # файловый объект уже генератор!
            yield line.strip()

for line in read_lines('data.txt'):
    process_line(line)

6. Конвейеры обработки данных (Pipeline)

def read_data():
    """Источник данных"""
    for i in range(1, 6):
        print(f"Читаю {i}")
        yield i

def filter_even(numbers):
    """Фильтр чётных чисел"""
    for n in numbers:
        if n % 2 == 0:
            print(f"Пропускаю {n}")
            continue
        print(f"Пропускаю нечётное {n}")
        yield n

def double(numbers):
    """Удвоение чисел"""
    for n in numbers:
        print(f"Удваиваю {n}")
        yield n * 2

# Конвейер — ленивые вычисления!
data = read_data()
filtered = filter_even(data)
doubled = double(filtered)

for result in doubled:
    print(f"Результат: {result}")

7. send() и throw() — двусторонняя коммуникация

def echo():
    """Генератор, который может принимать значения"""
    while True:
        value = yield  # ожидание значения
        print(f"Получено: {value}")

gen = echo()
next(gen)  # инициализация генератора (выполняет до первого yield)
gen.send("Hello")  # отправляет значение в генератор
gen.send("World")
# gen.close()  # закрытие генератора

# Практический пример: обработчик команд
def command_handler():
    print("Обработчик команд запущен")
    try:
        while True:
            cmd = yield  # ожидание команды
            if cmd == 'quit':
                print("Завершение")
                break
            else:
                print(f"Обработка: {cmd}")
    except GeneratorExit:
        print("Генератор закрыт")

handler = command_handler()
next(handler)  # инициализация
handler.send('start')
handler.send('process')
handler.close()

8. Практические примеры

Обработка CSV файла:

def parse_csv(filepath):
    """Парсит CSV и возвращает словари"""
    with open(filepath, 'r') as f:
        header = next(f).strip().split(',')
        for line in f:
            values = line.strip().split(',')
            yield dict(zip(header, values))

for row in parse_csv('data.csv'):
    print(row)  # {'name': 'Alice', 'age': '30', ...}

API с пагинацией:

import requests

def fetch_paginated_data(url, per_page=100):
    """Получает данные со всех страниц API"""
    page = 1
    while True:
        response = requests.get(url, params={'page': page, 'per_page': per_page})
        data = response.json()
        
        if not data:
            break  # нет больше данных
        
        for item in data:
            yield item
        
        page += 1

for user in fetch_paginated_data('https://api.example.com/users'):
    print(user['name'])

9. yield from — делегирование

def flatten(nested_list):
    """Flatten вложенного списка"""
    for item in nested_list:
        if isinstance(item, list):
            yield from flatten(item)  # yield from делегирует
        else:
            yield item

nested = [1, [2, 3, [4, 5]], 6]
print(list(flatten(nested)))  # [1, 2, 3, 4, 5, 6]

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

Используйте:

  • Больше данных, чем помещается в памяти
  • Обработка потоков (сеть, файлы, БД)
  • Бесконечные последовательности
  • Конвейеры обработки данных
  • Избежание создания временных списков

Не используйте:

  • Когда нужно несколько раз пройти по данным (генератор можно использовать только один раз)
  • Для простых функций с малым результатом
  • Когда индексация необходима (list) vs (generator)

Сравнение памяти:

import sys

# Функция со списком
def list_version(n):
    return [i for i in range(n)]

# Генератор
def gen_version(n):
    for i in range(n):
        yield i

print(sys.getsizeof(list_version(100)))      # ~920 байт
print(sys.getsizeof(gen_version(100)))       # ~128 байт
print(sys.getsizeof(list_version(1000000)))  # ~8.7 МБ
print(sys.getsizeof(gen_version(1000000)))   # ~128 байт (всегда!)