Ответ
Генераторы в Python — это специальный тип итераторов, которые позволяют лениво (по требованию) генерировать последовательности значений, не храня их все в памяти одновременно. Они создаются с помощью функций с ключевым словом yield или генераторных выражений.
Как работает yield:
- При вызове функции-генератора она возвращает объект-генератор, не начиная выполнение.
- При первом вызове
next()выполнение функции идет до первогоyield, возвращает значение и замораживает свое состояние (все локальные переменные сохраняются). - При следующем вызове
next()выполнение возобновляется с места послеyieldи продолжается до следующегоyield. - Когда функция завершается (или встречает
return), генерируется исключениеStopIteration.
Пример функции-генератора:
def countdown(n):
print("Генератор запущен")
while n > 0:
yield n # Возврат значения и пауза
n -= 1
print("Генератор завершен")
# Создание генератора
counter = countdown(3)
print(counter) # <generator object countdown at 0x...>
# Итерация по генератору
print(next(counter)) # Вывод: "Генератор запущен", затем: 3
print(next(counter)) # 2
print(next(counter)) # 1
# print(next(counter)) # Вызвало бы StopIteration
# Обычно используют в цикле for, который сам обрабатывает StopIteration
for number in countdown(5):
print(f"Обратный отсчет: {number}")
Генераторные выражения (аналог list comprehension):
# Создает список в памяти сразу
squares_list = [x**2 for x in range(1000000)] # Много памяти!
# Создает генератор, который вычисляет значения на лету
squares_gen = (x**2 for x in range(1000000)) # Экономия памяти
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
Ключевые преимущества:
- Экономия памяти: Идеально для обработки больших файлов, потоков данных или бесконечных последовательностей.
- Ленивые вычисления: Значения вычисляются только когда они действительно нужны.
- Сохранение состояния: Генератор "помнит" свое состояние между вызовами.
Практическое применение: Чтение больших файлов построчно.
def read_large_file(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
for line in file: # file объект сам является итератором
yield line.strip()
# В памяти одновременно находится только одна строка
for line in read_large_file("huge_log.txt"):
if "ERROR" in line:
process_error(line) Ответ 18+ 🔞
А, генераторы в Python, ёпта! Ну это же просто песня, а не фича, если честно. Представь себе, что у тебя есть задача обработать файл размером с твою совесть после вчерашнего — гигабайт на десять. И если ты тупо загрузишь его весь в память списком, то твоя программа накроется медным тазом, а оперативка взвоет, как Муму перед утоплением.
Так вот, генераторы — это такие хитрожопые функции, которые вместо того, чтобы вывалить тебе сразу всю кучу данных, подкидывают их по одной штуке, как заправский карманник. Сказал «дай» — получил значение. Не сказал — они и не парились, ждут.
Секрет их в волшебном слове yield. Это не просто return, который, как последний мудак, отдал всё и свалил. yield — он умный. Он отдал тебе значение, заморозил всю свою внутреннюю кухню (все переменные, счётчики, состояние) и заснул. А ты иди, работай. Как только ты снова позовёшь, он проснётся ровно на том же месте, где уснул, сделает следующий шаг и снова «йелднется». И так до победного, пока не кончится.
Смотри, как это выглядит, блядь:
def countdown(n):
print("Генератор запущен")
while n > 0:
yield n # Держи значение и стой, сука, на паузе!
n -= 1
print("Генератор завершен")
# Вот тут он ещё нихуя не делает. Просто создал объект-обещание.
counter = countdown(3)
print(counter) # <generator object countdown at 0x...> — вот он, красавец.
# А вот тут начинается магия. Тыкаешь в него next().
print(next(counter)) # Пишет "Генератор запущен" и выдаёт 3. И засыпает.
print(next(counter)) # Просыпается, уменьшает n, выдаёт 2. Спит.
print(next(counter)) # Просыпается, уменьшает, выдаёт 1. Спит.
# print(next(counter)) # А вот тут он проснётся, поймёт, что цикл кончился, и выплюнет StopIteration — типа, всё, пиздец, приехали.
Но нормальные люди, конечно, не тыкают в него next() как в говно пальцем. Его в цикл for пихают, он там сам всё красиво обходит и StopIteration ловит.
А ещё есть генераторные выражения — это как списковые включения, только для ленивых и умных. Сравни:
# Это — тупой быдлокод для мажоров с 64 гигами оперативки. Создаст список из ЛЯМА чисел СРАЗУ.
squares_list = [x**2 for x in range(1000000)] # Память, блядь, прощай!
# А это — изящно, как кот, сука, собака. Создаст генератор, который будет вычислять квадраты ПО ТРЕБОВАНИЮ.
squares_gen = (x**2 for x in range(1000000))
print(next(squares_gen)) # 0 — только посчитал ноль.
print(next(squares_gen)) # 1 — только посчитал один. Остальные 999998 чисел пока в небытии.
Зачем это всё, спросишь? Да затем, ёпта! Чтобы не обосраться с памятью. Самый классический пример — читать здоровенный лог-файл построчно и искать там ошибки.
def read_large_file(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
for line in file: # Кстати, сам файловый объект — он тоже итератор, умная жопа!
yield line.strip()
# И вот ты летишь по этому файлу, как угорелый.
# В памяти в каждый момент — максимум одна строка, а не весь файл целиком.
for line in read_large_file("huge_log.txt"):
if "ERROR" in line:
process_error(line) # Нашёл кринж — обработал.
Вот и вся философия. Вместо того чтобы тащить на себе весь воз данных, генераторы подвозят их на тележке по одному кирпичику. Экономия памяти — овердохуищная, а ленивые вычисления — это вообще святое. Красота, блядь, а не инструмент.