Получи случайную криптовалюту за регистрацию!

​Пример из практики! Генераторы часто применяют для одноразов | Goal Gesture программирование, IT

Пример из практики!

Генераторы часто применяют для одноразовой обработки данных по каким-то правилам. Например, в проекте с генератором текста на цепях Маркова у нас был такой фрагмент кода:

1. # отправляем в переменную всё содержимое текстового файла
2. text = open('che.txt', encoding='utf8').read()
3.
4. # разбиваем текст на отдельные слова (знаки препинания останутся рядом со своими словами)
5. corpus = text.split()
6.
7. # делаем новую функцию-генератор, которая определит пары слов
8. def make_pairs(corpus):
9. # перебираем все слова в корпусе, кроме последнего
10. for i in range(len(corpus)-1):
11. # генерируем новую пару и возвращаем её как результат работы функции
12. yield (corpus[i], corpus[i+1])
13.
14. # вызываем генератор и получаем все пары слов
15. pairs = make_pairs(corpus)

А вот что произошло здесь по шагам:

Мы открыли файл и записали всё его содержимое в переменную text.
С помощью встроенной функции split() мы разбили текст на отдельные слова и поместили все слова в отдельный массив. На этом этапе в массиве примерно 150 тысяч слов — для хранения такого количества данных компьютер выделил много памяти.
Мы пишем функцию-генератор. Каждый раз, когда к ней будут обращаться, она вернёт пару слов — текущее и следующее за ним.
В самом конце мы создаём новую переменную — pairs. Может показаться, что в ней сразу будут храниться все пары слов, но на самом деле это переменная-генератор. При каждом обращении к ней она вернёт новую пару слов и забудет о них.
В итоге у нас все слова хранятся в переменной corpus, а пары возвращаются «на лету» при каждом обращении к этой переменной.

Главный плюс генераторов — их можно указывать в качестве диапазона в циклах. На каждом шаге цикл получает новое значение от генератора и работает уже с ним. Как только у генератора заканчиваются варианты и он останавливается — цикл тоже останавливается.

Вот как мы работаем с этой переменной дальше:

1. # словарь, на старте пока пустой
2. word_dict = {}
3.
4. # перебираем все слова попарно из нашего списка пар
5. for word_1, word_2 in pairs:
6. # если первое слово уже есть в словаре
7. if word_1 in word_dict.keys():
8. # то добавляем второе слово как возможное продолжение первого
9. word_dict[word_1].append(word_2)

Здесь алгоритм работает так:

Делаем пустую переменную для словаря.
Запускаем цикл for и указываем переменную-генератор в качестве диапазона цикла.
Теперь на каждом шаге цикла он будет получать новую пару от генератора и обрабатывать её внутри цикла. При этом сами пары физически нигде не хранятся — их генератор каждый раз собирает на ходу.
Если бы мы не знали про генераторы, нам бы пришлось делать отдельный массив с парами слов и выделять под него память. В нашем проекте так сделать можно, но в реальных задачах с перебором большого количества данных такой подход может съесть всю память.

И что, всё теперь нужно делать на генераторах?
Нет, нужно просто знать, что есть такая возможность — собирать данные на ходу и при этом не тратить на это память. Если вам в проекте не нужны генераторы или экономия памяти будет мизерная — работайте без них. Но если нужно обработать огромный массив данных, из которых нужна только часть, то генераторы могут реально выручить.