Составление списков в Python 3 для начинающих

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

И тогда вы видите что-то вроде этого:

[x+1 if x >= 5 else x+2 for x in l]

Что это вообще такое ?? Это петля? Заявление if? И как это все в одной строке?

Это пример понимания списка в Python. Вопреки тому, что следует из названия, способность понимать подобный код, вероятно, является одним из самых больших недостатков использования этого инструмента, однако он может быть очень мощным в вашем арсенале. Во-первых, то, что может занять 5 или 6 строк кода, можно сократить до одной сжатой строки. Кроме того, понимание списков более эффективно с вычислительной точки зрения, и как только вы преодолеете трудности с пониманием того, как они работают, они могут сделать ваш код более простым и элегантным.

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

Что такое понимание списков?

Основная идея понимания списка состоит в том, чтобы взять группу элементов, каким-либо образом изменить элементы и вернуть их в виде списка. Все просто, правда? Ну, это еще не все ...

Общая идея понимания простого списка синтаксически выглядит так:

[do_something for each_thing in a_group_of_things]

Другими словами, часть do_something должна представлять то, что вы хотите для вывода. Часть each_thing представляет повторяющуюся переменную или каждый из исходных элементов, когда вы их просматриваете. Часть a_group_of_things представляет ввод или то, что мы повторяем. Это может быть что угодно, содержащее группу элементов. Чаще всего это будет список, но это может быть что угодно: кортеж, словарь, серия панд, набор или даже строка (группа символов). Если подумать обо всем в приведенном выше примере после do_something, он выглядит и ведет себя примерно так же, как и простой цикл for. Так что это аналогично:

for each_thing in a_group_of_things:
    do_something

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

Как использовать на практике

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

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

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

new_numbers = []
for number in numbers:
    number += 1
    new_numbers.append(number)

Список new_numbers теперь будет выглядеть так:

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

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

new_numbers = [number + 1 for number in numbers]

Вот и все! Мы фактически сократили четыре линии в одну и сделали меньше работы для себя и наших компьютеров. Нам не нужно беспокоиться о настройке for цикла или создании пустого списка и индивидуальном добавлении каждого элемента. Легко, правда?

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

new_numbers = [round(number / 3) for number in numbers]

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

[0, 0, 1, 1, 1, 2, 2, 2, 3, 3]

Что, если бы я хотел преобразовать числа в буквы от «a» до «j», используя понимание списка. Я мог бы сделать словарь, в котором числа будут ключами, а соответствующие буквы - значениями. Затем я могу использовать это в понимании списка для создания списка букв, например:

numbers_to_letters = {
    0: 'a',
    1: 'b',
    2: 'c',
    3: 'd',
    4: 'e',
    5: 'f',
    6: 'g',
    7: 'h',
    8: 'i',
    9: 'j'
}
letters = [numbers_to_letters[num] for num in numbers_to_letters]

Список letters теперь будет выводиться в:

['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

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

def remove_vowels(word):
    vowels = ['a', 'e', 'i', 'o', 'u']
    for letter in word:
        if letter in vowels:
            word = word.replace(letter, '')
    return word

words = ['car', 'house', 'plant', 'fisherman', 'picnic', 'rodeo']
new_words = [remove_vowels(word) for word in words]

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

new_words теперь будет выводиться в:

['cr', 'hs', 'plnt', 'fshrmn', 'pcnc', 'rd']

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

Условные выражения внутри списков

Понимание списков также позволяет нам использовать операторы if и else, придавая им еще более конкретную функциональность. Это довольно круто, хотя опять же синтаксис может быть немного сложным. Скажем, я хочу использовать наш исходный пример, но на этот раз изменил только нечетные числа и оставил остальные числа как есть. Мы можем сделать это, просто поместив операторы if и else внутри понимания списка:

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
new_numbers = [number + 1 if number % 2 == 1 else number for number in numbers]

Теперь для new_numbers у нас есть следующее:

[0, 2, 2, 4, 4, 6, 6, 8, 8, 10]

Порядок if else и for сначала может показаться немного странным. Для условной логики в понимании списка, как в приведенном выше примере, вы хотите думать об этом следующим образом:

[result_if_condition_met if condition else result_if_condition_not_met for element in group_of_elements]

Важное замечание: без оператора else все, что не соответствует условию, не будет возвращено вообще. Понимание списка полностью удалит этот элемент из выходного списка. Бывают моменты, когда мы действительно можем захотеть это сделать. Этот метод известен как фильтрация, который использует понимание списка для возврата только определенных элементов в группе на основе определенного условия или набора условий. К сожалению, Python не упрощает нам синтаксис. Это одна из самых запутанных вещей в понимании списков. Если вы не используете оператор else, оператор if необходимо переместить в конец понимания списка, например:

[result for element in group_of_elements if condition]

Вот простой пример фильтрации для нашего numbers списка выше:

new_numbers = [number for number in numbers if number % 2 == 1]

Теперь new_numbers будет содержать только нечетные числа:

[1, 3, 5, 7, 9]

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

new_numbers = [number + 1 for number in numbers if number % 2 == 1]

Выход:

[2, 4, 6, 8, 10]

Вы даже можете фильтровать по нескольким условиям или, другими словами, вкладывать одно условие в другое. Например, такой код:

new_numbers = [number for number in numbers if number % 2 == 1 if number < 5]

Дает это:

[1, 3]

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

А теперь самое интересное ... несколько операторов if и else в одном понимании списка. Да, мы действительно можем это сделать!

Концептуально это очень похоже на использование операторов if, elif и else в цикле for, однако при составлении списков мы не используем elif, и синтаксис, конечно, сильно отличается. Давайте посмотрим на код ниже:

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
new_numbers = []
for number in numbers:
    if number < 5:
        new_numbers.append(number)
    elif number == 7:
        new_numbers.append('seven')
    else:
        new_numbers.append('big')

Этот код вернет следующий список для new_numbers:

[0, 1, 2, 3, 4, 'big', 'big', 'seven', 'big', 'big']

Теперь вот эквивалент этого в понимании списка:

new_numbers = [number if number < 5 else 
               'seven' if number == 7 else 
               'big' for number in numbers]

Это гораздо более лаконичный и питонический код, который возвращает точно такой же результат. Вместо того, чтобы использовать elif, мы делаем это, используя условные выражения if и else в этом конкретном порядке. Второй if действует так же, как elif в версии этого for цикла. Вот более общий взгляд на несколько условных выражений при понимании списков:

[result_1 if condition_1 else
 result_2 if condition_2 else
 result_3 if condition_3 else
 result_if_no_conditions_met for element in group_of_elements]

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

Вложенные описания списков

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

[[do_something for item in inner_list] for inner_list in outer_list]

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

[[do a list comp] for inner_list in outer_list]

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

Длинная версия:

words = ['car', 'house', 'plant', 'fisherman', 'picnic', 'rodeo']
new_words = []
for word in words:
    vowels = ['a', 'e', 'i', 'o', 'u']
    for letter in word:
        if letter in vowels:
            word = word.replace(letter, '')
    new_words.append(word)

Лучше:

words = ['car', 'house', 'plant', 'fisherman', 'picnic', 'rodeo']
vowels = ['a', 'e', 'i', 'o', 'u']
new_words = [''.join([letter for letter in word if letter not in vowels]) for word in words]

Выход:

['cr', 'hs', 'plnt', 'fshrmn', 'pcnc', 'rd']

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

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

Удачи!