Курс по Python: https://stepik.org/course/100707
На этом занятии мы с вами поговорим о функциях-генераторах. Но вначале вернемся к
выражениям-генераторам, которые рассматривали на предыдущем занятии. Давайте
предположим, что нам нужны средние арифметические значения для разных
последовательностей целых чисел:
1,
2, 3, 4, 5, 6, 7, 8, 9, 10
2,
3, 4, 5, 6, 7, 8, 9, 10
3,
4, 5, 6, 7, 8, 9, 10
4,
5, 6, 7, 8, 9, 10
5,
6, 7, 8, 9, 10
6,
7, 8, 9, 10
7,
8, 9, 10
8,
9, 10
9, 10
Для этого мы
могли бы записать следующее выражение-генератор:
N = 10
a = (sum(range(i, N+1))/len(range(i, N+1)) for i in range(N))
Но оно не очень
удобно для восприятия и редактирования и, кроме того, здесь дважды записана
функция range() при
вычислении среднего значения. Поправить это можно двумя способами. В первом,
объявить собственную функцию для вычисления среднего арифметического и вызвать
ее в генераторе:
def avg(start, stop, step=1):
a = range(start, stop, step)
return sum(a) / len(a)
N = 10
a = (avg(i, N + 1) for i in range(1, N))
print(list(a))
А во втором
способе – создать собственную функцию-генератор, которая бы на выходе
выдавала нужные значения. Давайте для начала запишем простую функцию-генератор,
а потом вернемся к нашей исходной задаче. Функция будет просто возвращать
значения списка:
def get_list():
for x in [1, 2, 3, 4]:
yield x
Смотрите, здесь
в цикле записан новый для нас оператор yield, который
возвращает текущее значение x и «замораживает» состояние функции до
следующего обращения к ней (в том числе и все локальные переменные). Именно так
определяются функции-генераторы. Если мы сейчас ее вызовем:
то, смотрите,
переменная d ссылается на
объект-генератор, то есть, мы здесь имеем дело с генератором, значения которого
можно перебирать с помощью функции next():
print(next(d))
print(next(d))
Или, через цикл:
d = get_list()
for i in d:
print(i, end=" ")
В этом и
заключается роль оператора yield. Он превращает обычную функцию в
генератор и при каждом вызове функции next()
активизируется функция-генератор, возвращает очередное значение и
«замораживает» свое состояние вместе с локальными переменными до следующего
вызова функции next(). (Показываем это в режиме отладки).
Надеюсь, вы
теперь хорошо представляете, как работает оператор yield и простая
функция-генератор. Давайте вернемся к нашей исходной задаче и перепишем функцию
avg() с
использованием оператора yield (создаем еще
одну):
def avg_gen(N, step=1):
for i in range(1, N):
a = range(i, N+1, step)
yield sum(a) / len(a)
Принцип здесь
тот же самый, на каждой итерации цикла будет возвращаться новое вычисленное
среднее арифметическое значение. Далее, мы можем воспользоваться этой
функцией-генератором и отобразить все ее значения, используя функцию list():
b = avg_gen(N)
print(list(b))
Как видите,
результат полностью совпадает с первоначальным. То есть, мы заменили
выражение-генератор на функцию-генератор. Ну и, как всегда, возникает вопрос,
зачем это все было надо? Почему бы не пользоваться обычными генераторами? Что
нам мешает это делать? В целом, ничего. Преимущество здесь, главным образом, в
удобстве использования. В выражении генератора мы можем записать лишь один
оператор для формирования значения, а в функции-генераторе – произвольный
фрагмент программы, реализующий нужную нам логику формирования очередного
значения. В этом ключевое отличие функции-генератора от обычного генератора.
В заключение
этого занятия приведу еще один пример использования функции-генератора. Предположим,
что мы хотим найти все начальные индексы слова «генератор» в текстовом файле lesson_54.txt.
Для этого вначале откроем этот файл на чтение:
try:
with open("lesson_54.txt", encoding="utf-8") as file:
a = find_word(file, "генератор")
print(list(a))
except FileNotFoundError:
print("Файл не найден!")
except:
print("Ошибка обработки файла!")
И внутри блока try вызовем
функцию-генератор, которая и будет последовательно возвращать индексы
найденного слова «генератор». Саму функцию определим выше (по программе),
следующим образом:
def find_word(f, word):
g_indx = 0
for line in f:
indx = 0
while(indx != -1):
indx = line.find(word, indx)
if indx > -1:
yield g_indx + indx
indx += 1
g_indx += len(line)
Мы здесь в
первом цикле читаем файл по строкам. Во втором вложенном цикле while ищем указанное
слово в строке, используя метод find(). И, если этот метод находит заданный
фрагмент, то есть, возвращает значение больше -1, то функция генерирует на
выходе значение индекса найденного слова как g_indx + indx. Здесь g_indx – это смещение
по тексту для текущей строки, то есть, в ней мы суммируем длины предыдущих
строк, чтобы сформировать индекс слова в тексте, а не в строке.
После запуска
этой программы видим все найденные индексы данного слова по тексту. Как видите,
мы в функции описали нетривиальную логику алгоритма поиска слова в текстовом файле.
Сделать это с помощью обычного генератора было бы сложнее. В этом и
преимущество функций-генераторов – они позволяют сразу, в одном месте кода,
описывать нужный нам функционал.
На этом мы
завершим с вами очередное занятие. Для закрепления материала, как всегда,
пройдите практические задания и переходите к следующему уроку.
Курс по Python: https://stepik.org/course/100707