Выражения-генераторы списков, словарей, множеств, итераторов
Представьте, что нам нужно создать список из чисел от 1 до 100, не кратных 3 (те, которые при делении на 3 дают ненулевой остаток).
Можно создать такой список через цикл for:
numbers = [] for number in range(1, 101): if number % 3: numbers.append(number)
Получился код на 4 строки. При этом мы создаём пустой список, а потом каждый раз его заполняем по одному элементу.
Генераторы списков
Однако в Python можно создать подобный список в одну строку, не добавляя числа поэлементно:
numbers = [number for number in range(1, 101) if number % 3]
Как соотносятся цикл и генератор?
- numbers = [] -> [...] - В генераторе список создаётся сразу внутри квадратных скобок
- for number in range(1, 101): -> for number in range(1, 101) - Цикл for остаётся таким же (разве что не нужно двоеточие, поскольку в генераторе нет вложенных блоков с необходимостью дополнительного отступа)
- if number % 3: -> if number % 3 - Условие (или условия, их может быть несколько как в цикле, так и в генераторе) остаётся таким же
- numbers.append(number) -> number - В генераторе списка элемент добавляется напрямую, без использования метода append
Генераторы можно вкладывать друг в друга для создания более сложных структур данных. Например, создание таблицы умножения:
>>> multiplication_table = [[i * j for j in range(1, 5)] for i in range(1, 5)] >>> print(multiplication_table) [[1, 2, 3, 4], [2, 4, 6, 8], [3, 6, 9, 12], [4, 8, 12, 16]]
Вложенные генераторы
Вложенные генераторы позволяют создавать более сложные списки, включающие в себя комбинации элементов из нескольких последовательностей.
Например, создадим список пар чисел, где первое число от 1 до 3, а второе от 1 до 2:
pairs = [(x, y) for x in range(1, 4) for y in range(1, 3)]
Это эквивалентно следующим циклам:
pairs = [] for x in range(1, 4): for y in range(1, 3): pairs.append((x, y))
Вложенные генераторы удобно использовать, если необходима фильтрацию на каждом уровне цикла. Например, создадим список пар чисел, где первое число - нечётное от 1 до 5, а второе от 1 до 2, при этом исключая те пары, где сумма чисел равна 5:
pairs = [(x, y) for x in range(1, 6) if x % 2 for y in range(1, 3) if x + y != 5]
Это эквивалентно следующему циклу:
pairs = [] for x in range(1, 6): if x % 2: for y in range(1, 3): if x + y != 5: pairs.append((x, y))
Генераторы множеств
Подобным же образом можно создавать множества, только вместо квадратных скобок - фигурные:
numbers_unique = {number for number in range(1, 101) if number % 3}
Генераторы словарей
С помощью выражения-генератора можно создавать и словари (здесь в качестве примера мы возьмём значения для ключа, равное квадрату числа):
numbers = {number: number ** 2 for number in range(1, 101) if number % 3}
Генераторы итерируемых объектов
А что же будет, если скобки круглые? Нет, не генератор кортежей. Это будет генератор лениво итерируемого объекта:
>>> numbers = (number for number in range(1, 11) if number % 3) >>> print(numbers) <generator object <genexpr> at 0x0000016351173780> >>> for number in numbers: ... print(number) ... 1 2 4 5 7 8 10
- Занимает меньше памяти
- Выполняется ровно один раз, после чего во второй раз в цикле for он будет пустым
- Каждый элемент вычисляется только в момент, когда он понадобится. Все ошибки при генерации элементов будут встречены лишь внутри цикла, в котором этот итератор будет использован
С примером такого итератора мы уже встречались, это объект range.
Когда уместны генераторы
Генераторы удобны и лаконичны, когда нужно создать коллекцию на основе простой логики или фильтрации. Они делают код компактным и читаемым. Однако есть случаи, когда генераторы могут быть неуместны:
- Сложные логические условия и вычисления: Если логика создания элементов сложна и занимает много строк, использование генераторов сделает код трудным для понимания
- Многоуровневые вложенные циклы: При глубокой вложенности циклов генераторы могут ухудшить читаемость
- Отладка: Генераторы могут быть сложнее для отладки, особенно если в процессе создания коллекции возникают ошибки
Использование генераторов оправдано, когда нужно создать коллекцию быстро и понятно, избегая лишнего кода и циклов.