Python: коллекции, часть 4/4: Выражения-генераторы

December 21st 2021

4. Выражения-генераторы


Выражения-генераторы (generator expressions) доступны, начиная с Python 2.4. Основное их отличие от генераторов коллекций в том, что они выдают элемент по-одному, не загружая в память сразу всю коллекцию.

UPD: Еще раз обратите внимание на этот момент: если мы создаем большую структуру данных без использования генератора, то она загружается в память целиком, соответственно, это увеличивает расход памяти Вашим приложением, а в крайних случаях памяти может просто не хватить и Ваше приложение «упадет» с MemoryError. В случае использования выражения-генератора, такого не происходит, так как элементы создаются по-одному, в момент обращения.

Пример выражения-генератора:
list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
my_gen = (i for i in list_a)    # выражение-генератор
print(next(my_gen))     # -2 - получаем очередной элемент генератора
print(next(my_gen))     # -1 - получаем очередной элемент генератора

Особенности выражений-генераторов


  1. Генаратор нельзя писать без скобок — это синтаксическая ошибка.
    # my_gen = i for i in list_a      # SyntaxError: invalid syntax

  2. При передаче в функцию дополнительные скобки необязательны
    list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
    my_sum = sum(i for i in list_a)
    # my_sum = sum((i for i in list_a))  # так тоже можно
    print(my_sum)   # 12

  3. Нельзя получить длину функцией len()
    # my_len = len(i for i in list_a)  # TypeError: object of type 'generator' has no len()

  4. Нельзя распечатать элементы функцией print()
    print(my_gen)   # <generator object <genexpr> at 0x7f162db32af0>
    

  5. Обратите внимание, что после прохождения по выражению-генератору оно остается пустым!
    list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
    my_gen = (i for i in list_a)
    print(sum(my_gen))  # 12
    print(sum(my_gen))  # 0

  6. Выражение-генератор может быть бесконечным.
    import itertools
    inf_gen = (x for x in itertools.count())  # бесконечный генератор от 0 to бесконечности!
    Будьте осторожны в работе с такими генераторами, так как при не правильном использовании «эффект» будет как от бесконечного цикла.

  7. К выражению-генератору не применимы срезы!
    list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
    my_gen = (i for i in list_a)
    my_gen_sliced = my_gen[1:3]
    # TypeError: 'generator' object is not subscriptable

  8. Из генератора легко получать нужную коллекцию. Это подробно рассматривается в следующей главе.

5. Генерация стандартных коллекций


5.1 Создание коллекций из выражения-генератора


Создание коллекций из выражения-генератора с помощью функций list(), tuple(), set(), frozenset()

Примечание: Так можно создать и неизменное множество и кортеж, так как неизменными они станет уже после генерации.

Внимание: Для строки такой способ не работает! Синтаксис создания генератора словаря таким образом имеет свои особенности, он рассмотрен в следующем под-разделе.

  1. Передачей готового выражения-генератора присвоенного переменной в функцию создания коллекции.

    list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
    my_gen = (i for i in list_a)   # выражение-генератор
    my_list = list(my_gen) 
    print(my_list)          # [-2, -1, 0, 1, 2, 3, 4, 5]

  2. Написание выражения-генератора сразу внутри скобок вызываемой функции создания коллекции.

    list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
    my_list = list(i for i in list_a)
    print(my_list)          # [-2, -1, 0, 1, 2, 3, 4, 5]

    То же самое для кортежа, множества и неизменного множества
    # кортеж
    my_tuple = tuple(i for i in list_a)
    print(my_tuple)         # (-2, -1, 0, 1, 2, 3, 4, 5)
    
    # множество
    my_set = set(i for i in list_a)
    print(my_set)           # {0, 1, 2, 3, 4, 5, -1, -2}
    
    # неизменное множество
    my_frozenset = frozenset(i for i in list_a)
    print(my_frozenset)     # frozenset({0, 1, 2, 3, 4, 5, -1, -2})

5.2 Специальный синтаксис генераторов коллекций


В отличии от выражения-генератора, которое выдает значение по-одному, не загружая всю коллекцию в память, при использовании генераторов коллекций, коллекция генерируется сразу целиком.

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

Обратите внимание, что для генерации множества и словаря используются одинаковые скобки, разница в том, что у словаря указывается двойной элемент ключ: значение.

  1. Генератор списка (list comprehension)

    list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
    my_list = [i for i in list_a]
    print(my_list)          # [-2, -1, 0, 1, 2, 3, 4, 5]

    Не пишите круглые скобки в квадратных!

    list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
    my_list = [(i for i in list_a)]
    print(my_list)          # [<generator object <genexpr> at 0x7fb81103bf68>]

  2. Генератор множества (set comprehension)

    list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
    my_set= {i for i in list_a}
    print(my_set)       # {0, 1, 2, 3, 4, 5, -1, -2} - порядок случаен

  3. Генератор словаря (dictionary comprehension)
    переворачивание словаря

    dict_abc = {'a': 1, 'b': 2, 'c': 3, 'd': 3}
    dict_123 = {v: k for k, v in dict_abc.items()}
    print(dict_123)  # {1: 'a', 2: 'b', 3: 'd'}
                     # Обратите внимание, мы потеряли "с"! Так как значения были одинаковы, 
                     # то когда они стали ключами, только последнее значение сохранилось.

    Словарь из списка:
    list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
    dict_a = {x: x**2 for x in list_a}
    print(dict_a)   # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, -2: 4, -1: 1, 5: 25}

    Важно! Такой синтаксис создания словаря работает только в фигурных скобках, выражение-генератор так создать нельзя, для этого используется немного другой синтаксис :
    # dict_gen = (x: x**2 for x in list_a)      # SyntaxError: invalid syntax
    dict_gen = ((x, x ** 2) for x in list_a)    # Корректный вариант генератора-выражения для словаря
    # dict_a = dict(x: x**2 for x in list_a)    # SyntaxError: invalid syntax
    dict_a = dict((x, x ** 2) for x in list_a)  # Корректный вариант синтаксиса от @longclaps

5.3 Генерация строк


Для создания строки вместо синтаксиса выражений-генераторов используется метод строки .join(), которому в качестве аргументов можно передать выражение генератор.
Обратите внимание: элементы коллекции для объединения в строку должны быть строками!

list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
# используем генератор прямо в .join() одновременно приводя элементы к строковому типу
my_str = ''.join(str(x) for x in list_a)
print(my_str)  # -2-1012345