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().

nums = [n for n in range(1,6)]
print(nums)

Результат
[1, 2, 3, 4, 5]

В этом примере каждое значение диапазона присваивается переменной n. Каждое значение возвращается неизменным и добавляется в новый список. Это — та самая n перед циклом for.

В качестве итерируемого объекта не обязательно должна быть функция range(). Это может быть любое итерируемое значение

List comprehension с изменением

Теперь пойдем чуть дальше и добавим изменение для каждого значения в цикле.

nums = [1, 2, 3, 4, 5]
squares = [n*n for n in nums]
print(squares) 

Результат
[1, 4, 9, 16, 25]

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

Во-вторых, list comprehension создает список, где каждое значение — это возведенное в квадрат значения оригинального списка.

List comprehension с if

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

nums = [1, 2, 3, 4, 5]
odd_squares = [n*n for n in nums if n%2 == 1]
print(odd_squares)

Результат
[1, 9, 25]

Инструкция if идет после цикла — в данном случае порядок играет роль.

List comprehension с вложенным циклом for

В последнем примере рассмотрим пример со вложенным циклом for.

matrix = [[x for x in range(1, 4)] for y in range(1, 4)]
print(matrix)


Результат
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]

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

matrix = [
    [x for x in range(1, 4)]
    for y in range(1, 4)
]
print(matrix)

Последний пример. Создадим список дней рождения из списка словарей. Для этого используем знакомые тактики.

people = [{
  "first_name": "Василий",
  "last_name": "Марков",
  "birthday": "9/25/1984"
}, {
  "first_name": "Регина",
  "last_name": "Павленко",
  "birthday": "8/21/1995"
}]

birthdays = [
  person[term]
  for person in people
  for term in person
  if term == "birthday"
]

print(birthdays)

В этом примере мы сперва перебираем peopleприсваивая каждый словарь person. После этого перебираем каждый идентификатор в словаре, присваивая ключи term.

Если значение term равно birthday, то значение person[term] добавляет в list comprehension.

['9/25/1984', '8/21/1995']

Теперь можете попробовать поработать с list comprehension на собственных примерах. Это сделает код более быстрым и компактным.

Представьте, что нам нужно создать список, состоящий из квадратов чисел, определенных от 0 до N-1. Используя текущие знания, это можно было бы реализовать так:

A=[]
N=10
for x in range(N):
     A.append(x**2)
 
print(A)

А теперь этот же алгоритм с использованием list comprehensions:

N=10
A = [x**2 for x in range(N)]
print(A)

Смотрите, все создание списка записано в одну строчку. И, кроме того, этот вариант будет работать быстрее предыдущей программы, т.к. Python оптимизирует работу таких конструкций.

Давайте теперь разберемся в этом синтаксисе. Вначале мы указываем, что будем делать с переменной x. Казалось бы, переменная нигде не задана, а мы уже говорим что с ней делать. Да, это так, это такой элемент синтаксиса list comprehensions. Далее, через пробел мы записываем цикл for и уже там указываем эту переменную x и говорим как она будет меняться. То есть, эта временная переменная x существует только внутри списка и пропадает после его создания.

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

A=[]
N=10
for x in range(N):
     if x%2 == 0:
          A.append(x**2)
 
print(A)

А через list comprehensions это записывается следующим образом:

N=10
A = [x**2 for x in range(N) if x%2 == 0]
print(A)

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

Разумеется, вместо возведения в степень мы можем прописывать любые функциональные выражения, или записать просто x:

A = [x for x in range(N) if x%2 == 0]

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

A = [0.5*x+1 for x in range(N)]

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

cities = ["Москва", "Тверь", "Рязань", "Ярославль", "Владимир"]
A = [city for city in cities if len(city) < 7]
print(A)

Здесь мы формируем список из названий городов, длины которых меньше семи символов. По этому принципу можно строить самые разные алгоритмы формирования списков.

Генератор списков в Python (подробнее)

Несмотря на столь серьезное название, генератор списков в Python представляет собой не что иное, как простой и короткий способ создания списков. Этот элегантный способ был создан для того, чтобы упростить жизнь программистам, избавив от лишней «писанины».

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

Cоздание списка целых чисел от 0 до 10

1. Стандартный способ:
list=[]fori inrange(10):    
list.append(i)# вывод на экран:
print(list) 

# результат на экране:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 
2. Используем генератор списков:
list = [i for i in range(10)]# вывод на экран:
print(list)
# результат на экране:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Итоговые результаты обоих способов одинаковы, однако, конструкция с генератором списков выглядит более лаконичной (1 строка против 3-х!).

Рассмотрим подробнее, что же представляет собой конструкция генератора списков. В рассмотренном выше примере эта конструкция заключена в квадратные скобки и выглядит как [ i for i in range(10) ].

Квадратные скобки указывают на создаваемый в Python список.

А запись внутри скобок можно расшифровать как: [добавить i для i из промежутка целых чисел: (0, 10)].

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

new_list = [ добавить {имя_элемента} для {имя_элемента}, удовлетворяющего условиям {условия}] 

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

Создание списка натуральных чисел, кратных трем:

Так как ноль не относится к натуральным числам, то в данном примере будем рассматривать промежуток (1, 10)

1. Обычный способ:

a =[]

fori inrange(1,10):

    ifi%3==0:

        a.append(i)

# вывод на экран

print(a)

# результат на экране
[3, 6, 9]

2. Используем конструкцию генератора списков:

a =[i fori inrange(10) ifi%3==0]

# вывод на экран

print(a)

# результат на экране
[3, 6, 9]

В данном примере конструкция генератора списков позволила сократить код на 3 строки!

Замена символов в текстовых списках обычным способом и используя генератор списков в Python

Рассмотрим еще один пример удачного применения генератора списков. На этот раз в качестве исходных данных будет список text_list со словами. Нужно заменить в словах списка букву «к» на «т».

1. Стандартный способ:

text_list =["кот", "ком", "кем"]

new_text_list =[]

forword intext_list:

    word =word.replace("к", "т")

    new_text_list.append(word)

text_list =new_text_list

print(text_list)

 # результат на экране:
 ['тот', 'том', 'тем'] 

2. При помощи конструкции генератора списков:

text_list =["кот", "ком", "кем"]

text_list =[word.replace("к", "т") forword intext_list]

print(text_list)

# результат на экране:
 ['тот', 'том', 'тем'] 

https://www.youtube.com/watch?v=gMM7632kTkI&list=PLA0M1Bcd0w8xIdFNA95aQrwJ_GQJEV8ko