НОЧУ ДПО "Национальный открытый университет "ИНТУИТ"
Опубликован: 24.01.2021 | Доступ: свободный | Студентов: 1229 / 21 | Длительность: 03:57:00
Лекция 30:

Генераторы

< Лекция 1 || Лекция 30: 12

Смотреть на youtube

Итератор позволяет получить коллекцию элементов, генератор позволяет на основе этой коллекции создать новую коллекцию. Рассмотрим следующую задачу. Пусть Р - итерируемый объект. Цикл for позволяет получить коллекцию элементов р_1, р_2, \dots р_n, связанную с итерируемым объектом. Нам требуется создать новую коллекцию - список, состоящих из элементов рk, возможно прошедших фильтрацию, преобразованных в соответствии с заданным выражением. Например, дан список целых чисел, мы хотим отобрать из списка четные элементы и возвести их в квадрат, получив новый список. Нетрудно написать соответствующий код, решающий эту задачу:

def test1():    
    source = [3, 8, 12, 5, 4, 7]
    """Создание списка: Классический код """
    even_quadrat = []
    for ev in source:
        if ev % 2 == 0:
            even_quadrat.append (ev * ev)
    
    """Создание списка: Генератор (упакованный код)"""
    gen_even_quadrat = [ev * ev for ev in source if ev % 2 == 0]

    Print(even_quadrat)
    Print(gen_even_quadrat)

Результаты запуска этого теста:


Как видите хорошо понятный классический код в четыре строки решает поставленную задачу. Генератор, показанный в следующей строчке, позволяет сократить запись, упаковав код четырех строк в одну строку. По сути код остался тем же самым, изменения носят чисто синтаксический характер. Запись стала короче, но менее понятна, хотя бы из-за того, что оператор цикла и оператор if, записаны внутри скобок, а не стоят на местах, подобающих операторам программы.

В мире программистов Python генераторы широко используются. В чем их преимущество:

  • Краткость кода. Код короче, но менее понятный. Только ради краткости не стоило бы городить огород.
  • Генератор является выражением, поэтому его можно использовать всюду, где допустимы выражения. Это тоже не столь значимое преимущество, поскольку генератор - это длинное выражение, которое разумно именовать, как это сделано в нашем примере, прежде чем использовать в других выражениях.
  • Эффективность выполнения кода. Это серьезный аргумент в пользу генераторов. Код генератора выполняется быстрее, чем классический код, использующий цикл. Когда речь идет о работе со массивными итерируемыми объектами, эффективность выполнения кода может играть важную роль.
  • У генераторов есть и дополнительное преимущество, о котором поговорим чуть позже.

Пример генератора, который мы построили, не отражает всех возможностей этого инструмента. Генератор может быть значительно сложнее. Выражение генерируемого списка может зависеть от нескольких переменных, каждая из которых пробегает значения своей итерируемой последовательности, что требует реализации вложенных циклов. Генератор справляется и с такой ситуацией. Рассмотрим пример построения таблицы истинности логической функции, зависящей от нескольких переменных. Этот пример мы уже рассматривали при демонстрации работы итератора map. Теперь покажем решение, основанное на классическом коде и соответствующем генераторе:

def test2():
    """ Построение таблицы истинности логической функции"""
    """ Классический код"""
    L = [0, 1]
    T = [] 
    for x in L:
        for y in L:
            for z in L:
                T.append(x and y or z)
    """ Генератор """
    gen_T =[x and y or z for x in L for y in L for z in L]

    Print(T)
    Print(gen_T)

Результаты работы этого теста показывают, что генератор выполняет работу трех вложенных циклов:


Синтаксис генератора в общем случае задается следующим определением:

<генератор списка> ::= [<выражение(v1, v2, …vk)> 
for v1 in <итератор_1> if filter_1
…
for vk in <итератор_k> if filter_k]

В квадратных скобках записывается выражение, зависящее в общем случае от k переменных. Для каждой переменной записывается оператор for с соответствующим итератором и возможно с заданием фильтра для элементов.

Заметьте, никаких разделителей кроме пробелов в этой записи нет.

Семантика выполнения интуитивно понятна, - это семантика вложенных циклов. Циклы генерируют все возможные комбинации переменных с учетом фильтрации. К каждой комбинации применяется выражение, заданное в генераторе. Значение этого выражения становится очередным элементом генератора списка.

Понятно, что таким способом можно генерировать не только списки. Можно строить генераторы словарей, генераторы множеств. Но нельзя строить генераторы кортежей по понятной причине. Кортежи - это неизменяемый тип данных, а генератор строит по определению изменяемую структуру.

Синтаксически все генераторы имеют похожую структуру и отличаются лишь скобками. В генераторе множества и словаря используются фигурные, а не квадратные скобки. Генератор словаря отличается от генератора множества тем, что в словаре используется двоеточие, разделяющее ключ и значение.

Приведу пример создания генератора словаря:

def test3(): 
    """ Пример генератора словаря """
    Names = ['Петров', 'Леонов', 'Егоров']
    Marks = [5, 4, 5]
    dict = {name : mark for name, mark in zip(Names, Marks)}
    for key in dict:
        print (key, ' : ', dict[key], end = ' ')
    print()

Синтаксически генератор словарей отличается тем, что выражение генератора представляет пару ключ, значение, разделенную двоеточием, а оператор цикла for использует итератор zip, чтобы ключ и значение параллельно выбирались из соответствующих множеств.

Вот результаты запуска этого теста:


С генератором множеств все проще и понятнее:

def test4():
    """ Пример генератора множества """
    Marks = [5, 4, 5, 3, 5, 4]
    dif_marks = {dif_mark for dif_mark in Marks}
    Print(dif_marks)

Вот результаты работы теста:


< Лекция 1 || Лекция 30: 12
Елена Лаптева
Елена Лаптева

Думаю. что не смогу его закончить. Хотелось предупредить других - не тратьте зря время, ищите другой курс.

Михаил Сидоров
Михаил Сидоров

Если S - последовательность, то срез задается как S(i : j) и содержит j - i элементов,

а в примере используютс другие скобки - 

NL[1:3] = ["решили", "не", "искать"]

или это не срез, тогда, что это?