Перевод статьи «If You Find if..else in List Comprehensions Confusing, Read This, Else…».
Я долго шел путем проб и ошибок, вместо того чтобы разобраться. При написании list comprehension (русск. «списковое включение», «представление списка») я никак не мог запомнить, идет ли одиночное if перед for, а комбинация if..else после него, или наоборот (спойлер: наоборот). Но теперь всё будет иначе. Теперь мне всё совершенно ясно. И вам тоже будет ясно, когда прочитаете!
О чем речь?
Что же меня так долго путало? Начну с простого примера. Допустим, у вас есть список имен, и вы хотите оставить только те имена, которые состоят из более чем пяти букв. Это можно сделать с помощью следующего list comprehension:
names = ["Jim", "Audrey", "Clara", "Aaron", "Ishaan", "Amelia"] long_names = [name for name in names if len(name) > 5] print(long_names) # OUTPUT: ['Audrey', 'Ishaan', 'Amelia']
Это представление списка состоит из трех частей, стоящих в определенном порядке:
- Переменная с именем
name. - Цикл
forдля перебора спискаnames. - Часть с
ifдля определения, какие имена сохранить.
Но что, если вместо того, чтобы полностью отбрасывать более короткие имена, вы хотите заменить их плейсхолдером?
names = ["Jim", "Audrey", "Clara", "Aaron", "Ishaan", "Amelia"] long_names = [name if len(name) > 5 else "?" for name in names] print(long_names) # OUTPUT: ['?', 'Audrey', '?', '?', 'Ishaan', 'Amelia']
Порядок частей в list comprehension теперь другой:
- Переменная с именем
name[некоторые вещи неизменны]. - Часть с
if..elseдля определения, какие имена сохранить, а какие заменить. - Часть с
forдля перебора спискаnames.
Заметили разницу? Напоминает неуловимое движение фокусника. Вот оба представления списка, показанные вместе, с дополнительными пробелами, не предусмотренными PEP 8, только для улучшения видимости:
[ name for name in names if len(name) > 5 ] [ name if len(name) > 5 else "?" for name in names ]
Во втором случае if..else предшествовало for.
Бессмыслица какакя-то, верно? У вас есть два варианта:
- Смириться и просто запомнить две версии.
- Читать дальше.
Анатомия list comprehension (сокращенная версия)
Вот что говорит наш источник мудрости, то есть документация Python: «List Comprehension состоит из одного выражения, за которым следует как минимум одна операторная часть for и ноль или более операторных частей for или if».
Начнем с первого представления списка, все еще с несоответствующими пробелами:
[ name for name in names if len(name) > 5 ]
Перечитайте фразу из документации. Вот три части:
- Состоит из одного выражения… — идентификатор
name, который мы часто называем переменной, соответствует этому описанию. - …за которым следует как минимум одна операторная часть
for— в этом примере есть одно условиеfor, что соответствует части «как минимум одна». - … и ноль или более операторных частей
forилиif— в этом представлении списка есть один операторif, что удовлетворяет этой части описания.
А как насчет второго представления списка, того, что с if..else?
[ name if len(name) > 5 else "?" for name in names ]
Не торопитесь. Можете ли вы как-то соотнести его с описанием представления списка из документации?
…
Нет?
Это потому, что лишние пробелы, которые я добавил, неправильные. (Напоминаю, что я добавляю эти лишние пробелы, чтобы облегчить обсуждение — не делайте этого в реальном коде, иначе вы получите выговор от PEP 8!)
Вот тот же list comprehension с другим выделением значимых частей:
[ name if len(name) > 5 else "?" for name in names ]
В этом представлении списка есть только две части:
- Состоит из одного выражения… – да,
name if len(name) > 5 else "?"– это одно выражение. Подробнее об этом чуть позже. - …за которым следует как минимум одна операторная часть
for– здесь есть одно условиеfor, так что все в порядке. - … и ноль или более операторных частей
forилиif— в нашем list comprehension больше ничего не осталось, но это все равно удовлетворяет условию благодаря слову «ноль»!
Вы без проблем приняли пункты 2 и 3 выше как правильные. Но вам нужно больше убедительных доказательств, чтобы принять первый пункт?
Это одно выражение:
name if len(name) > 5 else "?"
Это условное выражение. Нет, нет, я не имел в виду условный оператор, это нечто другое.
Запутались? Я лишь недавно перестал всякий раз заново подсматривать разницу между оператором и выражением, когда мне нужно было использовать эти термины. Итак, небольшое отступление перед возвращением к list comprehension.
Операторы и выражения
Определения — это скучно, я знаю. (Признаюсь: я люблю определения, но никому не говорите.) Я буду краток.
Выражение (англ. expression) может быть оценено как значение. Вот простой способ определить, является ли что-то выражением: напишите его в строке REPL и нажмите Enter/Return. Выводится ли значение? Если да, то это выражение. Вот несколько примеров выражений:
5
# 5
1 + 1
# 2
int("5")
# 5
max(2, 5, 10)
# 10
10 == 5 + 5
# True
# # The next one assumes an import 'statement' first
random.randint(1, 10)
# 7
"hello" or []
# 'hello'
Все они возвращают значения. Следовательно, все они являются выражениями. Если вы можете поместить нечто после знака равенства в присваивании, то это выражение.
Оператор (англ. statement) — это более общее описание строки кода, поскольку операторы могут не оцениваться в какое-либо значение. Например, for _ in range(10): не оценивается в значение, как и while True: или if name == "Stephen".
Оператор присваивания является оператором:
my_name = "Stephen"
Часть после знака равенства является выражением, но вся строка присваивания является оператором.
А в последних версиях Python есть также выражение присваивания, часто называемое моржовым оператором:
my_name := "Stephen"
Это оценивается в значение:
final_var = (my_name := "Stephen") my_name # 'Stephen' final_var # 'Stephen'
Все выражения являются операторами. Но не все операторы являются выражениями.
Но я отвлекся. Вернёмся к основной теме этой статьи.
Условное выражение (тернарный оператор)
Если вы читаете это, то вы знакомы с условными операторами. Это одна из первых вещей, которые вы изучаете, например, if name == "Stephen". И вы можете строить блоки с помощью операторов if, elif и else. Но вы все это уже знаете.
Это операторы. Они не вычисляются в значение. Их нельзя поместить в правую часть знака равенства в присваивании.
Но есть еще и условное выражение:
name = "Stephen" name if len(name) > 5 else "?" # 'Stephen' name = "Bob" name if len(name) > 5 else "?" # '?'
Это выражение, поскольку оно вычисляется в значение. Возвращаемое значение — это либо то, что находится перед if, либо то, что находится после else, в зависимости от того, оценивается ли часть в середине как True или False.
А поскольку это выражение, при желании вы можете присвоить его значение имени переменной:
name = "Stephen" final_result = name if len(name) > 5 else "?" final_result # 'Stephen' name = "Bob" final_result = name if len(name) > 5 else "?" final_result # '?'
Это условное выражение часто называют тернарным оператором. Тернарный оператор — это любой оператор, который требует трех операндов, так же, как бинарный оператор требует двух операндов.
В Python есть много бинарных операторов, таких как ==, >, or, and, is и другие. Но есть только один тернарный оператор — условное выражение:
<первый операнд> if <второй операнд> else <третий операнд>
Итак, вернёмся к list comprehension:
[ name if len(name) > 5 else "?" for name in names ]
«Одно выражение», необходимое для представления списка, — это условное выражение (тернарный оператор!). Вот оно:
name if len(name) > 5 else "?"
Три операнда в этом тернарном операторе:
namelen(name) > 5"?"
Все три являются самостоятельными выражениями!
Описание представления списка требует как минимум одного условия for, которое у вас есть. Последняя часть описания представления списка понимания требует «ноль или более операторов for или if»; в этом представлении списка имеем «ноль».
В общем, все логично. (Важный совет: если вам кажется, что что-то в Python не логично, вам, вероятно, следует копнуть глубже. Тогда вы поймете.)
Теперь вам больше не понадобится метод проб и ошибок, чтобы запомнить, в каком порядке все идет в представлении списка.

