Генераторы списков (list comprehension) в Python Python

Генераторы списков (list comprehension) в Python

При программировании на Python в преобладающем большинстве случаев нам приходится генерировать новые списки путем незначительного изменения или сортировки элементов из уже существующих (имеющихся) у нас списков. Конечно же, такое генерирование новых списков на основе уже имеющихся можно было бы легко осуществить за счет использования старого доброго и всем известного цикла for. Но, с точки зрения Python, этот цикл является инструментарием общего назначения, который априори не может быть оптимизирован под вышеописанную нами, вполне конкретную, но в тоже время крайне распространенную, задачу генерации списков. Именно поэтому в арсенале возможностей Python предусмотрена отдельная специальная конструкция, оптимизированная под генерацию новых списков из уже существующих, как в отношении быстродействия, так и с точки зрения компактности кода.

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

Генерация списков за счет цикла for

Для начала за счет обычного цикла for из первоначального списка практикующих медиков list-physicians попробуем сгенерировать его модифицированную версию:

list_physicians = ["Врач семейной медицины", "Пульмонолог", "Нефролог",
                   "Отоларинголог", "Гинеколог", "Уролог", "Терапевт",
                   "Кардиолог", "Гастроэнтеролог", "Невролог",]

Допустим, посредством цикла for, перебирающего все элементы вышеприведенного списка list-physicians нам нужно сделать новый список выездных консультирующих специалистов - visiting-consultants, в котором специализации всех медиков прописать заглавными буквами:

visiting_consultants = []
for name in list_physicians:
    visiting_consultants.append(name.upper())

В итоге, мы получим следующий результат:

from pprint import pprint

list_physicians = ["Врач семейной медицины", "Пульмонолог", "Нефролог",
                   "Отоларинголог", "Гинеколог", "Уролог","Терапевт",
                   "Кардиолог", "Гастроэнтеролог", "Невролог",]

visiting_consultants = []
for name in list_physicians:
    visiting_consultants.append(name.upper())

pprint(visiting_consultants)

['ВРАЧ СЕМЕЙНОЙ МЕДИЦИНЫ',
 'ПУЛЬМОНОЛОГ',
 'НЕФРОЛОГ',
 'ОТОЛАРИНГОЛОГ',
 'ГИНЕКОЛОГ',
 'УРОЛОГ',
 'ТЕРАПЕВТ',
 'КАРДИОЛОГ',
 'ГАСТРОЭНТЕРОЛОГ',
 'НЕВРОЛОГ']

Переформатирование цикла for в генераторы списков

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

new_spisok = []
for element in old_spisok:
    new_spisok.append(nekotoraya_funktsiya(element)),

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

visiting_consultants = []
for name in list_physicians:
    visiting_consultants.append(name.upper())

А начнем мы с дублирования квадратных скобок ([]), означающих создание списка:

visiting_consultants = [
]

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

visiting_consultants = [
    name.upper()
]

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

visiting_consultants = [
    name.upper()
    for name in list_physicians
]

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

visiting_consultants = []
for name in list_physicians:
    visiting_consultants.append(name.upper())

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

visiting_consultants = [name.upper() for name in list_physicians]

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

Фильтрация элементов в генераторах списков

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

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

from pprint import pprint

list_physicians = ["Врач семейной медицины", "Пульмонолог", "Нефролог",
                   "Отоларинголог", "Гинеколог", "Уролог", "Терапевт",
                   "Кардиолог", "Гастроэнтеролог", "Невролог",]

long_specialization = []
for name in list_physicians:
    if len(name) > 10:
        long_specialization.append(name + ' - ' + str(len(name)) + ' симв.')

pprint(long_specialization)

['Врач семейной медицины - 22 симв.',
 'Пульмонолог - 11 симв.',
 'Отоларинголог - 13 симв.',
 'Гастроэнтеролог - 15 симв.']
Как видим, результат совпадает с нашими ожиданиями, так как все выведенные названия специализаций медиков имеют свыше 10-ти символов.

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

new_spisok = []
for element in old_spisok:
    if usloviye_na(element):
        new_spisok.append(nekotoraya_funktsiya(element)),

может быть всегда переформатирован в соответствующий генератор списков. Для этого, как и раннее попробуем поэтапно осуществить соответствующее переформатирование начиная с квадратных скобок ([]):

long_specialization = [
]

Внутри этих квадратных скобок, как и следовало ожидать, мы первым делом размещаем выражение name + ' - ' + str(len(name)) + ' симв.', преобразующее старые элементы нашего списка в новые:

long_specialization = [
    name + ' - ' + str(len(name)) + ' симв.'
]

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

long_specialization = [
    name + ' - ' + str(len(name)) + ' симв.'
   for name in list_physicians:
]

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

long_specialization = [
    name + ' - ' + str(len(name)) + ' симв.'
   for name in list_physicians
   if len(name) > 10
]

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

from pprint import pprint

list_physicians = ["Врач семейной медицины", "Пульмонолог", "Нефролог",
                   "Отоларинголог", "Гинеколог", "Уролог", "Терапевт",
                   "Кардиолог", "Гастроэнтеролог", "Невролог",]

long_specialization = [name + ' - ' + str(len(name)) + ' симв.'
                       for name in list_physicians if len(name) > 10]

pprint(long_specialization)

['Врач семейной медицины - 22 симв.',
 'Пульмонолог - 11 симв.',
 'Отоларинголог - 13 симв.',
 'Гастроэнтеролог - 15 симв.']

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

Переформатирование в генераторы списков вложенных циклов for

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

list_physicians = ["Врач семейной медицины", "Пульмонолог", "Нефролог",
                   "Отоларинголог", "Гинеколог", "Уролог", "Терапевт",
                   "Кардиолог", "Гастроэнтеролог", "Невролог",]

zaglavnyye_bukvy = []
for name in list_physicians:
    for char in name:
        if char.isupper():
            zaglavnyye_bukvy.append(char)

pprint(zaglavnyye_bukvy)

['В', 'П', 'Н', 'О', 'Г', 'У', 'Т', 'К', 'Г', 'Н']

Следует отметить, что переформатирование в генераторы списков действительно может быть допустимо при нескольких вложенных for циклах и нескольких вложенных операторах if, но только при условии наличия во всех этих вложенных структурах всего одной append строки.

Итак, переформатирование раннее приведенного вложенного цикла в генератор списков мы начинаем все с тех же квадратных скобок, куда сначала добавляются найденные заглавные буквы (char) из внутреннего вложенного цикла с append строкой:

zaglavnyye_bukvy = [
    char
]

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

zaglavnyye_bukvy = [
    char
   for name in list_physicians
   for char in name
   if char.isupper()
]

Обратите внимание, что все for и if строки, добавляются из вложенного цикла в генератор списков в абсолютно аналогичном порядке.

Переформатирование в генераторы списков for циклов нестандартной структуры

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

Смоделируем данную ситуация на нашем уже знакомом примере списка практикующих медиков. Допустим, нам требуется для тех медицинских специализаций, названия которых имеют свыше 10 символов, дополнительно указать их длину, а для всех остальных специализаций - преобразовать их названия к верхнему регистру. Очевидно, что реализующий поставленную задачу пример не укладывается в стандартную структуру for цикла, поскольку дополнительно имеет else блок и несколько append строк:

from pprint import pprint

list_physicians = ["Врач семейной медицины", "Пульмонолог", "Нефролог",
                   "Отоларинголог", "Гинеколог", "Уролог", "Терапевт",
                   "Кардиолог", "Гастроэнтеролог", "Невролог",]

difficult_list = []
for name in list_physicians:
    if len(name) > 10:
        difficult_list.append(name + ' - ' + str(len(name)) + ' симв.')
    else:
        difficult_list.append(name.upper())

pprint(difficult_list)

['Врач семейной медицины - 22 симв.',
 'Пульмонолог - 11 симв.',
 'НЕФРОЛОГ',
 'Отоларинголог - 13 симв.',
 'ГИНЕКОЛОГ',
 'УРОЛОГ',
 'ТЕРАПЕВТ',
 'КАРДИОЛОГ',
 'Гастроэнтеролог - 15 симв.',
 'НЕВРОЛОГ'] 

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

def helper_function (name):
    if len(name) > 10:
        return name + ' - ' + str(len(name)) + ' симв.'
    else:
        return name.upper()

Таким образом, поскольку данная вспомогательная функция helper_function отвечает за логику if- else из раннее приведенной программы, а также возвращает один единственный однозначный результат, то мы легко можем переписать наш for цикл, так чтобы helper_function добавляла полученный ею результат в наш новый список:

difficult_list = []
for name in list_physicians:
    difficult_list.append(helper_function(name))

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

difficult_list = [
    helper_function(name)
    for name in list_physicians
]

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

from pprint import pprint

def helper_function (name):
    if len(name) > 10:
        return name + ' - ' + str(len(name)) + ' симв.'
    else:
        return name.upper()

list_physicians = ["Врач семейной медицины", "Пульмонолог", "Нефролог",
                   "Отоларинголог", "Гинеколог", "Уролог", "Терапевт",
                   "Кардиолог", "Гастроэнтеролог", "Невролог",]

difficult_list = [helper_function(name) for name in list_physicians]

pprint(difficult_list)

['Врач семейной медицины - 22 симв.',
 'Пульмонолог - 11 симв.',
 'НЕФРОЛОГ',
 'Отоларинголог - 13 симв.',
 'ГИНЕКОЛОГ',
 'УРОЛОГ',
 'ТЕРАПЕВТ',
 'КАРДИОЛОГ',
 'Гастроэнтеролог - 15 симв.',
 'НЕВРОЛОГ']

Выводы

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

Вместе с тем, следует учесть, что далеко не все for циклы могут быть преобразованы (переформатированы) в генераторы списков. И здесь даже неважно, сколько у вас в исходном коде присутствует вложенных for циклов или условных if конструкций. Тут, показателем возможности перевода for циклов в генераторы списков прежде всего служить наличие лишь одной append строки и достаточно простая логика самых for, ограниченная применением только for и if конструкций. Если же ваши for циклы имеют более сложную логику, то вероятность их перевода в генераторы списков будет обусловлена лишь тем, сможете ли вы эти циклы привести к более простому (каноническому) виду с одной append строкой.

Практический Python для начинающих
Практический Python для начинающих

Станьте junior Python программистом за 7 месяцев

 7 месяцев

Возможно будет интересно

🏆 Hello, world! Python
Новичок
🏆 Hello, world!

Мы вчера запустили новый www.pylot.me. Должны были в следующую среду, но запустили вчера.

2022-10-04
Как практиковаться в Python? Python
Новичок
Как практиковаться в Python?

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

2022-10-19
Условные конструкции и сопоставление структурных шаблонов Шпаргалки
Новичок
Условные конструкции и сопоставление структурных шаблонов

Шпаргалка по условным конструкциям и сопоставлению структурных шаблонов

2022-11-09