Однострочный for на Python: списковое включение и генераторные выражения

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

  1. Списковые включения (генераторы списков, Listcomps).
  2. Словарные включения (генераторы словарей, Dictcomps).
  3. Множественные включения (генераторы множеств, Setcomps).
  4. Генераторные выражения (GenExp).

Списковое включение

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

new_list = [выражение for элемент in последовательность if условие]

Часть с условием if указывается опционально.

Пример

Давайте создадим список чисел, исключив из него все отрицательные значения  —  для начала решим задачу с помощью обычного цикла for:

numbers = [4, -2, 7, -4, 19]
new_nums = []
for num in numbers:
    if num > 0:
        new_nums.append(num)
print(new_nums)

Результат:

[4, 7, 19]

Генератор словарей

Для быстрого, лаконичного и наглядного создания словарей язык программирования Python предлагает воспользоваться специальным сокращением для циклического перебора элементов, известным как “генератор словарей”.

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

new_dict = { ключ:значение for (ключ,значение) in dict.items() if условие }

Пример

Давайте возведем в квадрат все числовые значения словаря при помощи словарного включения:

data = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

squared = {k:v*v for (k,v) in data.items()}

print(squared)

Результат:

{'a': 1, 'b': 4, 'c': 9, 'd': 16, 'e': 25}

Генератор множеств

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

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

new_set = { выражение for элемент in последовательность if условие }

Пример

Давайте создадим список произвольных чисел и множество парных чисел на его основе:

numbers = [13, 21, 14, 24, 53, 62] 
  
filtered_nums = set() 
  
for num in numbers: 
    if num % 2 == 0: 
        filtered_nums.add(num) 
  
print(filtered_nums)

Результат:

{24, 62, 14}

При помощи генератора множеств подобная программа пишется в одну строчку:

filtered_nums = {num for num in numbers if num % 2 == 0}

print(filtered_nums)

Результат:

{24, 62, 14}

Генераторные выражения

Подобно включениям, выражение генератора предлагает сокращенный синтаксис для цикла for.

Синтаксис генераторных выражений следующий:

( выражение for элемент in последовательность if условие )

Пример

Давайте возведем в квадрат все четные числа списка и отбросим все нечетные.

Для начала решим задачу с помощью обычного цикла for:

def square_even(numbers):
    for number in numbers:
        if number % 2 == 0:
            yield(number * number)
            
numbers = [1, 2, 3, 4, 5, 6]
squared_numbers = square_even(numbers)

for number in squared_numbers:
    print(number)

Результат:

4
16
36

С помощью выражения-генератора можно вообще забыть о потребности в функции square_even() и сделать то же самое с помощью одной строки кода:

squared_numbers = (num * num for num in numbers if num % 2 == 0)

for number in squared_numbers: 
    print(number)

Результат:

4
16
36

Генератор кортежей

При взгляде на синтаксис генераторных выражений, сразу же в голову приходит мысль: как написать кортежное включение?

Генераторное выражение можно передавать в качестве параметра любой функции-конструктору стандартных типов данных Python:

tuple(выражение for элемент in последовательность if условие)

Когда включения не подходят?

Только что вы узнали о четырех типах генераторов последовательностей в Python.

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

Короче говоря, не используйте включения, когда они снижают качество вашего кода!

Хороший пример  —  это работа со вложенными циклами for. Если написать вложенный цикл for в виде включения, то код станет короче на несколько строк, но его качество рискует ухудшиться.

Давайте рассмотрим матрицу; она часто представляется в Python как список из списков. Если вы хотите отобразить матрицу в одном измерении, то можете применить двойное списковое включение:

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]
 
flat_matrix = [num for row in matrix for num in row]
print(flat_matrix)

Результат:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

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

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]
 
flat_matrix = []

for row in matrix:
    for num in row:
        flat_matrix.append(num)

print(flat_matrix)

Результат:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

Производительность генераторных выражений

Наконец, давайте посмотрим на производительность включений в сравнении с обычным циклом for:

Списковое включение выполняется на 10% быстрее, чем цикл for

Аналогичный бенчмарк для генераторов множеств и словарей дает схожие результаты: включение работает немного быстрей.

Для генераторных выражений сравнение с циклом for + yield бессмысленно: оба возвращают объект-генератор почти мгновенно. Время не тратится на вычисление значений, поскольку генераторы не хранят значения.

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


Выводы

Включения превращают циклы for в однострочные выражения.

Python поддерживает четыре вида включений для стандартных структур данных:

  1. Списковые включения.
  2. Словарные включения.
  3. Множественные включения.
  4. Генераторные выражения.

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

Включения выполняются быстрее, чем цикл for, однако разница  —  всего 10%, поэтому вместо производительности отдавайте предпочтение качеству кода.

Спасибо за прочтение, счастливого программирования!


Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Artturi Jalli: Comprehensions in Python — Write Shorter For Loops

Предыдущая статьяОпытный программист теряет работу
Следующая статьяЧем веб-дизайн отличается от front end разработки?