Обложка канала

Goal Gesture программирование, IT

Новости, статьи и полезные материалы из мира IT

Goal Gesture программирование, IT

4 года назад
Открыть в
Пример из практики! Генераторы часто применяют для одноразовой обработки данных по каким-то правилам. Например, в проекте с генератором текста на цепях Маркова у нас был такой фрагмент кода: 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 и указываем переменную-генератор в качестве диапазона цикла. Теперь на каждом шаге цикла он будет получать новую пару от генератора и обрабатывать её внутри цикла. При этом сами пары физически нигде не хранятся — их генератор каждый раз собирает на ходу. ❌ Если бы мы не знали про генераторы, нам бы пришлось делать отдельный массив с парами слов и выделять под него память. В нашем проекте так сделать можно, но в реальных задачах с перебором большого количества данных такой подход может съесть всю память. И что, всё теперь нужно делать на генераторах? Нет, нужно просто знать, что есть такая возможность — собирать данные на ходу и при этом не тратить на это память. Если вам в проекте не нужны генераторы или экономия памяти будет мизерная — работайте без них. Но если нужно обработать огромный массив данных, из которых нужна только часть, то генераторы могут реально выручить.