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 - получаем очередной элемент генератора
Особенности выражений-генераторов
- Генаратор нельзя писать без скобок — это синтаксическая ошибка.
# my_gen = i for i in list_a # SyntaxError: invalid syntax
- При передаче в функцию дополнительные скобки необязательны
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
- Нельзя получить длину функцией len()
# my_len = len(i for i in list_a) # TypeError: object of type 'generator' has no len()
- Нельзя распечатать элементы функцией print()
print(my_gen) # <generator object <genexpr> at 0x7f162db32af0>
- Обратите внимание, что после прохождения по выражению-генератору оно остается пустым!
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
- Выражение-генератор может быть бесконечным.
import itertools
inf_gen = (x for x in itertools.count()) # бесконечный генератор от 0 to бесконечности!
Будьте осторожны в работе с такими генераторами, так как при не правильном использовании «эффект» будет как от бесконечного цикла.
- К выражению-генератору не применимы срезы!
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
- Из генератора легко получать нужную коллекцию. Это подробно рассматривается в следующей главе.
5. Генерация стандартных коллекций
5.1 Создание коллекций из выражения-генератора
Создание коллекций из выражения-генератора с помощью функций list(), tuple(), set(), frozenset()
Примечание: Так можно создать и неизменное множество и кортеж, так как неизменными они станет уже после генерации.
Внимание: Для строки такой способ не работает! Синтаксис создания генератора словаря таким образом имеет свои особенности, он рассмотрен в следующем под-разделе.
- Передачей готового выражения-генератора присвоенного переменной в функцию создания коллекции.
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]
- Написание выражения-генератора сразу внутри скобок вызываемой функции создания коллекции.
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 Специальный синтаксис генераторов коллекций
В отличии от выражения-генератора, которое выдает значение по-одному, не загружая всю коллекцию в память, при использовании генераторов коллекций, коллекция генерируется сразу целиком.
Соответственно, вместо особенности выражений-генераторов перечисленных выше, такая коллекция будет обладать всеми стандартными свойствами характерными для коллекции данного типа.
Обратите внимание, что для генерации множества и словаря используются одинаковые скобки, разница в том, что у словаря указывается двойной элемент ключ: значение.
- Генератор списка (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>]
- Генератор множества (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} - порядок случаен
- Генератор словаря (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