Что такое list comprehension
Перейти к содержимому

Что такое list comprehension

  • автор:

Что такое list comprehension

Функциональность list comprehension предоставляет более краткий и лаконичный синтаксис для создания списков на основе других наборов данных. Она имеет следующий синтаксис:

newlist = [expression for item in iterable (if condition)]

Синтаксис list comprehension состоит из следующих компонентов:

  • iterable : перебираемый источник данных, в качестве которого может выступать список, множество, последовательность, либо даже функция, которая возвращает набор данных, например, range()
  • item : извлекаемый из источника данных элемент
  • expression : выражение, которое возвращает некоторое значение. Это значение затем попадает в генерируемый список
  • condition : условие, которому должны соответствовать извлекаемые из источника данных элементы. Если элемент НЕ удовлетворяет условию, то он НЕ выбирается. Необязательный параметр.

Рассмотрим небольшой пример. Допустим, нам надо выбрать из списка все числа, которые больше 0. В обшем случае мы бы могли написать так:

numbers = [-3, -2, -1, 0, 1, 2, 3] positive_numbers = [] for n in numbers: if n > 0: positive_numbers.append(n) print(positive_numbers) # [1, 2, 3]

Теперь изменим этот код, применив list comprehension :

numbers = [-3, -2, -1, 0, 1, 2, 3] positive_numbers = [n for n in numbers if n > 0] print(positive_numbers) # [1, 2, 3]

Выражение [n for n in numbers if n > 0] говорит выбрать из списка numbers каждый элемент в переменную n, если n больше 0 и возврать n в результирующий список.

источник данных iterable

В качестве источника данных iterable может использоваться любой перебираемый объект, например, другой список, словарь и т.д. Например, функция range() возвращает все числя нуля до указанного порога не включая:

numbers = [n for n in range(10)] print(numbers) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

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

dictionary = words = [word for word in dictionary] print(words) # ['red', 'blue', 'green']

Возвращение результата

Параметр expression представляет выражение, которое возвращает некоторое значение. Это значение затем помещается в генерируемый список. В примерах выше это был текущий элемент, который извлекается из источника данных:

numbers = [-3, -2, -1, 0, 1, 2, 3] new_numbers = [n for n in numbers] print(new_numbers) # [-3, -2, -1, 0, 1, 2, 3]

Так, в данном случае параметр expression представляет непосредственно извлекаемый из списка numbers элемент n. Но это могут быть и более сложные значения. Например, возвратим удвоенное значение числа:

numbers = [-3, -2, -1, 0, 1, 2, 3] new_numbers = [n * 2 for n in numbers] print(new_numbers) # [-6, -4, -2, 0, 2, 4, 6]

Здесь expression представляет выражение n * 2

Это могут быть и более сложные выражения:

numbers = [-3, -2, -1, 0, 1, 2, 3] new_numbers = [n * 2 if n > 0 else n for n in numbers] print(new_numbers) # [-3, -2, -1, 0, 2, 4, 6]

Здесь параметр expression представляет выражение n * 2 if n > 0 else n . В данном случае мы говорим возвратить значение n * 2, если n > 0, иначе возвратить n.

В expression можно производить различные трансформации с данными. Например, возвратим также из словаря значение по ключу:

dictionary = words = [f": " for key in dictionary] print(words) # ['red: красный', 'blue: синий', 'green: зеленый']

Условие

Условие — параметр condition определяет фильтр для выбора элементов из источника данных. Применим условие для конкретизации выборки, например, выберем только четные числа:

numbers = [n for n in range(10) if n % 2 == 0] print(numbers) # [0, 2, 4, 6, 8]

Выберем только те ключи из словаря, длина которых больше 3:

dictionary = words = [f": " for key in dictionary if len(key) > 3] print(words) # ['blue: синий', 'green: зеленый']

Руководство по использованию list comprehension

У каждого языка программирования есть свои особенности и преимущества. Одна из культовых фишек Python — list comprehension (редко переводится на русский, но можно использовать определение «генератора списка»). Comprehension легко читать, и их используют как начинающие, так и опытные разработчики.

List comprehension — это упрощенный подход к созданию списка, который задействует цикл for, а также инструкции if-else для определения того, что в итоге окажется в финальном списке.

Преимущества list comprehension

У list comprehension есть три основных преимущества.

  1. Простота. List comprehension позволяют избавиться от циклов for, а также делают код более понятным. В JavaScript, например, есть нечто похожее в виде map() и filter() , но новичками они воспринимаются сложнее.
  2. Скорость. List comprehension быстрее for-циклов, которые он и заменяет. Это один из первых пунктов при рефакторинге Python-кода.
  3. Принципы функционального программирования. Это не так важно для начинающих, но функциональное программирование — это подход, при котором изменяемые данные не меняются. Поскольку list comprehensions создают новый список, не меняя существующий, их можно отнести к функциональному программированию.

Создание первого list comprehension

List comprehension записывается в квадратных скобках и задействует цикл for. В процессе создается новый список, куда добавляются все элементы оригинального. По мере добавления элементов их можно изменять.

Для начала возьмем простейший пример: создадим список из цифр от 1 до 5, используя функцию range() .

List Comprehensions (генераторы списков) в Python

В Python присутствует синтаксическая конструкция, которая позволяет в одну строку заполнять списки простыми или сложными значениями. Называется она — генераторы списков или List Comprehensions. Сейчас мы поговорим об операциях с ними и расскажем о том, как их использовать в своих задачах.

Експертний курс від laba: Business English for HR.
Мова бізнесу для HR-професіоналів.

1. Шаблон построения генератора списка

Перевод термина List Comprehensions как «генераторы списков» не совсем удачен, однако мы будем использовать именно его, так как он ближе к пониманию искомой сути, чем, например, такие варианты перевода как «список понятий» или «абстракция списков».

Все генераторы списков строятся по одинаковому шаблону, который выглядит так:

Динамічний курс від skvot: Візуалізація екстер’єрів у 3DS MAX.
Мистецтво в 3DS MAX.

[выражение for val in коллекция] , где выражение — выражение, описывающее переменную, val — переменная.

На основе этого шаблона для примера напишем простой генератор списка:

a = [1 for i in range(8)] print(a)

Вместо выражения мы просто запишем единицу. Так как в нашем цикле for стоит число восемь, мы восемь раз подставляем наше выражение — единицу. На экране появились восемь единиц — это наш список.

Указав вместо единицы значение двойки мы получим восемь двоек, а если изменим число циклов с восьми на десять — получим десять двоек и так далее.

Также мы можем указывать переменную, например, как i :

a = [i for i in range(8)] print(a)

Так как в нашем случае переменная i будет принимать значение от нуля до семи, то эти a = [i%2 for i in range(8)]

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

a = [i**2 for i in range(8)] print(a) [0, 1, 4, 9, 16, 25, 36, 49]

Обратите внимание — все то же самое мы можем сделать в цикле стандартными средствами Python.

Професійний курс від laba: Проджект-менеджмент в ІТ.
Ефективне управління проектами.

Это может выглядеть, скажем, вот так:

N = 8 a = [0] * N for i in range(N): a[i] = i ** 2 print(a)

Результат тот же самый [0, 1, 4, 9, 16, 25, 36, 49] . Однако вариант с генератором списка более удобен — одна строчка вместо нескольких. К тому же скорость работы в случае с List Comprehensions будет выше по сравнению с реализацией через цикл for.

Также можно задавать диапазон перебираемых значений. Например, следующее выражение выводит остатки чисел, деленных на четыре в диапазоне от 8 до 16:

a = [i%4 for i in range(8, 17)] print(a) [0, 1, 2, 3, 0, 1, 2, 3, 0]

2. Коллекция из текста в генераторе списка

В качестве коллекции мы можем обходить и другие итерабельные объекты, например, строки.

a = [i for i in 'HighloadToday'] print(a)

Так как мы внутри цикла перебираем все буквы нашей строки, на экране мы получим отдельные символы, то есть список будет заполнен символами строки:

['H', 'i', 'g', 'h', 'l', 'o', 'a', 'd', 'T', 'o', 'd', 'a', 'y']

Строки можно умножать. В результате мы получаем список, в котором символы продублированы заданное число раз.

Например, при умножении нашего примера на три мы имеем следующее:

a = [i*3 for i in 'HighloadToday'] print(a) ['HHH', 'iii', 'ggg', 'hhh', 'lll', 'ooo', 'aaa', 'ddd', 'TTT', 'ooo', 'ddd', 'aaa', 'yyy']

Также внутри выражения мы можем использовать различные функции. Например, функция ord находит код таблицы символов ASCII

a = [ord(i) for i in 'HighloadToday'] print(a) [72, 105, 103, 104, 108, 111, 97, 100, 84, 111, 100, 97, 121]

Для следующего примера мы возьмем число циклов десять раз, а в качестве функции — функцию случайных чисел randint, предварительно импортировав модуль random.

Пусть эта функция возвращает нам какое-то число из указанного диапазона.

Вдохновляючий курс від skvot: Створення текстів.
Мистецтво слова та стилю.

import random a = [random.randint(-10, 10) for i in range(10)] print(a)

Результатом такого списка будут десять случайных чисел:

[-7, -1, 8, 9, -10, -7, 8, 5, 6, -7]

3. Работа с несколькими генераторами списков

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

import random a = [random.randint(-10, 10) for i in range(10)] print(a) b = [abs(elements) for elements in a] print(b) [9, 7, -8, 10, 4, 9, -6, 0, 3, -1] [9, 7, 8, 10, 4, 9, 6, 0, 3, 1]

В списке b остались значения только по модулю.

Генераторы списков позволяют менять исходные элементы списка. Так, следующая запись приведет к тому, что все элементы списка увеличатся на единицу:

import random a = [random.randint(-10, 10) for i in range(10)] print(a) a = [elements+1 for elements in a] print(a) [4, -2, -5, 3, 3, -4, 3, 1, -6, -6] [5, -1, -4, 4, 4, -3, 4, 2, -5, -5]

4. Условные конструкции

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

[выражение for val in коллекция if условие]

Предположим, мы составляем список b , в котором находятся только четные элементы. Добавим это условие в наш код. Каждый элемент генерируемого списка должен делиться на 2 с остатком 0:

import random a = [random.randint(-10, 10) for i in range(10)] print(a) b = [elements for elements in a if elements % 2 == 0] print(b) [-10, -8, -3, 3, 8, -5, -7, -10, 3, 9] [-10, -8, 8, -10]

Условие можно также сделать сложным, скажем, добавив новое требование — выводимые элементы должны быть меньше нуля и четными:

import random a = [random.randint(-10, 10) for i in range(10)] print(a) b = [elements for elements in a if elements < 0 and elements % 2 == 0] print(b) [-6, 6, -4, -4, -6, 10, -5, -1, -10, -7] [-6, -4, -4, -6, -10]

5. Списки: ввод данных

Предположим, нам необходимо ввести через пробел несколько чисел. Это делается несложной командой:

a = input() print (a)

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

a = input().split() print(a) 11 7 4 1 111 22 ['11', '7', '4', '1', '111', '22']

Теперь мы получили список, в котором можно преобразовать каждый элемент в целое число. Для этого задействуем функцию int() , которое преобразует значение к целому типу.

Далее мы будем использовать генератор списков по уже известному нам шаблону:

a = input().split() a = [int(i) for i in a] print(a)

Теперь наш список содержит числа:

111 22 5 15 7 6 41 14 [111, 22, 5, 15, 7, 6, 41, 14]

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

s = list(map (int, input().split)) print (s)

6. Инициализация двухмерных списков

С помощью генераторов можно инициализировать двумерные списки. Предположим, у нас есть шесть строк и семь столбцов из элементов «единица».

n = 6 m = 7 a = [[1]*m for i in range(n)] print[a] [[1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1]]

Мы получили массив, записанный в одну строку. Чтобы отобразить его более привычно, пройдемся циклом

for i in a: print(i)

И получим вывод:

Зададим какой-нибудь элемент этой матрицы. Пусть, например, в четвертой строке четвертый элемент равен восьми:

n = 6 m = 7 a = [[1]*m for i in range(n)] a[3][3] = 8 for i in a: print(i)

И получим такой вывод:

7. Вложенные циклы в генераторах списков

Генераторы списков могут включать в себя двойные циклы. Предположим, у нас есть список [1, 2, 3] и мы обойдем им символы abc.

a = [(i, j) for i in 'abc' for j in [1, 2, 3]] print(a) [('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 2), ('b', 3), ('c', 1), ('c', 2), ('c', 3)]

Как видим, у нас каждое значение списка сочеталось с каждым значением элемента abc.

Такие вложенные циклы генераторов можно сопровождать какими-то условиями. Например, возьмем произведение элементов списков и поставим условие отбора значений больше (или равное) десяти.

a = [(i * j) for i in [2, 3, 4, 5] for j in [1, 2, 3] if i * j >= 10] print(a)

Мы получили три пары таких значений — [12, 10, 15].

8. Функция map с генераторами списков

Теперь поговорим о том, как можно использовать функцию map с генераторами списков. Функция map принимает функцию (func), а затем итерабильные последовательности — списки, кортежи, словари, строки и так далее.

class map (object) map (func, *iterables) - - > map object

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

Допустим, мы имеем дело со списком a = [-1, 2, -3, 4, 5] и мы можем вызвать функцию map, в которой передадим функцию abs.

a = [-1, 2, -3, 4, 5] b = map (abs, a) print(b)

Итератор представляет собой такую коллекцию, которую можно, во-первых, итерировать, то есть обходить все эти элементы коллекции в цикле for, а во-вторых, использовать функции, которые действуют над коллекциями.

Такие, как, например, «максимум», «минимум» и т.д. Функцию map оборачиваем функцией list, превращая переменную b в список.

a = [-1, 2, -3, 4, 5] b = list (map (abs, a)) print(b)

К каждому элементу списка применилась функция abs.

[1, 2, 3, 4, 5]

Перепишем код этой программы, с помощью генератора списков.

a = [-1, 2, -3, 4, 5] b = list(map(abs, a)) c = [abs(i) for i in a] print(c)

Результат тот же самый [1, 2, 3, 4, 5].

В нашем примере мы передали встроенную функцию abs, однако с тем же успехом мы можем передавать функции, которые создадим сами при помощи инструкции def.

Создадим такую функцию:

def f(x): return x ** 2 a = [-1, 2, -3, 4, 5] b = list(map(f, a)) print(b) [1, 4, 9, 16, 25]

Функция map будет брать значения из нашего списка a и передавать их в функцию f в качестве локальной переменной x, после чего вернет квадрат этого значения.

Занесем в список a строки, чтобы рассмотреть еще один пример. Для строк вы тоже можете вызывать встроенные функции, которые они поддерживают. Одной из таких функций является len :

a = ['Highload', 'Today'] b = list(map(len, a)) print(b)

В консоли отображаются длины слов «Highload» и «Today»:

В качестве функций можно передавать не только самописные функции, но и методы этих объектов, в нашем случае это, например, Upper.

Чтобы его вызвать, мы должны прописать встроенный объект str и далее название его метода:

a = ['Highload', 'Today'] b = list(map(str.upper, a)) print(b)

В результате мы имеем список из слов, написанных заглавными буквами:

['HIGHLOAD', 'TODAY']

Функцию map можно также передавать анонимной функции. Они создаются при помощи инструкции lambda. Причем здесь мы должны указать, что наша функция должна принимать один аргумент, в который будут поочередно присваиваться строки нашего списка а .

Мы берем его срез от начала до конца с шагом минус один. Тем самым мы получаем строку наоборот:

a = ['Highload', 'Today'] b = list(map(lambda x: x[::-1], a)) print(b) ['daolhgiH', 'yadoT']

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

a = ['Highload', 'Today'] b = list(map(lambda x: x[::-1], a)) c = [i[::-1]for i in a] print(c) ['daolhgiH', 'yadoT']

9. Проблемы с генераторами списков

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

Если длина такого ряда составляет сто чисел, тысячу или, даже, миллион — компьютер справится с задачей. Однако если количество суммируемых элементов слишком велико, скажем, миллиард, компьютер может не «переварить» такую задачу.

Дело в том, что Python попытается создать список с миллиардом целых чисел, что потребует значительных накладных ресурсов для создания огромного списка и сохранения его в памяти.

a = sum([i * i for i in range(1000000)]) print(a)

В этом случае имеет смысл воспользоваться иными инструментами Python для реализации данной задачи. Например, можно использовать генератор, который не помещает весь список в память, а в каждый отдельный момент удерживает только одно значение — то, которое он возвращает.

Заключение

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

Для закрепления материала рекомендуем вам посмотреть видео, где подробно объяснена данная тема:

Что такое list comprehension? Зачем оно? Какие ещё бывают?

List comprehension трудно перевести правильно на русский, потому, раз он генерирует новый список, будем называть его просто генератором списков. Это одна из самых приятных вещей в python, научившись писать которую, будешь применять её везде. Как это выглядит? Пожалуй Вы точно видели записи такого вида:

squares = [x ** 2 for x in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

это и есть генератор списка, результатом которого будет список квадратов последовательности 0..9 . Что здесь происходит? Это обычный цикл for , только записан в удобочитаемом виде, в развёрнутом виде это выглядело бы так:

squares = [] for x in range(10): squares.append(x ** 2) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

Вот тут и видна разница в этих записях — генератор списка условно можно назвать синтаксическим сахаром для цикла for , но у них разное время выполнения. Под капотом генератор списка также использует цикл for но выигрывает по скорости из-за того, то не вызывает метод append у списка (подробности здесь).

Так же как и в обычных циклах в генераторах можно применять условия:

>>> odds = [x for x in range(10) if x % 2 != 0] # [1, 3, 5, 7, 9] 

если в условии нужен else , то всё условие пишется до for :

>>> [x ** 2 if x % 2 == 0 else x ** 3 for x in range(10)] # [0, 1, 4, 27, 16, 125, 36, 343, 64, 729] 

Гораздо удобнее итерироваться по двум спискам:

first = [] for x in range(1, 5): for y in range(5, 1, -1): if x != y: first.append((x, y)) second = [(x, y) for x in range(1, 5) for y in range(5, 1, -1) if x != y] >>> first == second True 

Отличный пример из документации (раскрытие списка списков), усложним его:

>>> vec = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[10, 11, 12], [13, 14, 15], [16, 17, 18]]] >>> [digit for lst in vec for elem in lst for digit in elem] # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] 

Подобным образом с помощью генераторов списков мы можем создать словарь:

>>> dict([(key, value) for (key, value) in zip([1, 2, 3], ['a', 'b', 'c'])]) # 

И тут нас ожидает приятная новость — в python есть и генераторы словарей, записываются так же, как и генераторы списков, только в фигурных скобках < . >:

>>> key: value for key, value in zip([1, 2, 3], ['a', 'b', 'c'])> # 

К слову, словарь можно создать и без генератора — dict(zip(list1, list2)) .

Если мы генератор списка запишем в круглых скобках, то получим обычный генератор:

>>> gen = (x for x in range(10)) >>> gen generator object genexpr> at 0x7f635076ea98> >>> gen.__next__() 0 >>> gen.__next__() 1 ....... 

Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *