Тернарная логическая операция

borntohack

змееуст
Команда форума
Модератор
Апр 22, 2020
78
62
18
39
Москва, РФ
Пожалуй, одно из самых непонятных явлений при анализе чужого кода для новичков - это наличие в нем тернарных логических операций. Они выглядят пугающе, и зачастую вовсе не очевидно, как они работают. Это мешает интерпретировать и анализировать код "глазами". Однако, если в них разобраться - это повысит качество вашего кода, и позволит вам усовершенствовать Ваш навык поиска ошибок.

Давайте разбираться, что это, и зачем:

Определение:
Тернарная логическая операция - это операция условного присваивания значения переменной. Т.е. в зависимости от выполнения условия присваивания, переменная примет либо одно, либо другое значение.

Структура тернарной логической операции:
Python:
var = значение_если_условие_истинно if условие else значение_если_условие_ложно

Как мы привыкли писать подобные кейсы:
Python:
a = 3
b = some_function_result()
if b < 3:
    a = 5
Здесь, если результат some_function_result() будет меньше 3, то переменная a примет значение 5, во всех остальных случаях она останется в значении 3

Тот же код можно записать в виде тернарной операции:
Python:
a = 5 if some_function_result() < 3 else 3

Разбираемся:
В начале python выполнит условие тернарной операции (some_function_result() < 3). Если оно истинно(True) - переменной a будет присвоено значение, лежащее до условного оператора if (5), если же оно ложно(False) - значение, лежащее после условного оператора else (3)

Тернарные логические операции можно встретить в связке со списковыми включениями, и окончательно запутаться, где и что выполняется.
объясню на примере такого когда:
Python:
some_lst = [i**2 if i%5 else i for i in range(100) if not i%2]
Здесь мы видим сразу два условия if, при этом первое относится к работе с итератором, и представляет тернарную логическую операцию (возвести значение в квадрат при условии делимости этого значения на 5, во всех остальных случаях не возводить, но работать только с четными значениями из списка от 0 до 100 (условие спискового включения, отфильтровывающее итерации с ложным условием (нечетные числа)).
В данном коде вначале выполняется обработка спискового включения [i for i in range(100) if not i%2], и для каждого полученного таким образом i, выполняется тернарная логическая операция i = i**2 if i%5 else i

Так же можно встретить вложенность тернарных логических операций:
Python:
var = 3 if some_func() else 5 if some_func2() else 8
Главенство операций и их вложенность определяется интерпретатором как и в случае списковых включений слева направо.
Таким образом сначала вычислится some_func(). Если оно истинно - то var = 3, если ложно, то var = 5 if some_func2() else 8 - уже знакомая нам обычная тернарная операция.
Вложенность операций может быть теоретически бесконечной.

У тернарной логической операции существует еще одна, более непонятная запись:
Код:
var = (если_условие_ложно, если_условие_истинно)[условие]

т.е. для уже приведенного примера, такая нотация выглядит как:
Python:
a = (5,3)[some_function_results() < 3]

Однако, прошу обратить внимание, что разные здесь не только записи, но и логика работы.
В тех случаях, когда значения вычисляемы, а не задаваемы - в первом случае вычисляется только одно значение в зависимости от условия, а во втором - вычисляются оба. Это можно проиллюстрировать на примере ошибочного вычисления с заведомо верным условием:
Python:
a = 10*2 if 3 < 5 else 10/0
b = (10/0, 10*2)[3<5]
При этом, несмотря на наличие ошибки DivisionByZero - первая строка отработает корректно, поскольку значение после else не будет вычисляться по причине истинности условия
Вторая же строка вернет вам ошибку вычисления (DivizonByZero) - поскольку кортеж должен содержать конечные значения для присваивания, и перед логической операцией он должен быть вычислен.

Учитывайте, пожалуйста, эти нюансы в своих разработках

Так же есть сокращенная запись тернарной логической операции, связанная с приведением результата выполнения функции к булевой величине:
Python:
a = some_func() or 5
Это не совсем тернарная операция. По сути, она всегда присвоит переменной a возвращаемое значение some_func, если только some_func не вернула False,0 или None.
Следует очень осторожно использовать такую конструкцию при работе с числами, поскольку число "0" является булевым False даже если это логичный и разумный результат вычисления some_func в этом случае a примет значение 5.

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

stud_55

Модератор
Команда форума
Модератор
Апр 3, 2020
1 522
672
113
Код:
var = (если_условие_истинно, если_условие_ложно)[условие]
В этом примере значения нужно поменять местами, то есть должно быть так:
Код:
var = (если_условие_ложно, если_условие_истинно)[условие]
Эта конструкция работает так: сначала идет проверка условия (в результате получается True или False), потом результат проверки служит индексом для кортежа в начале выражения. То есть из кортежа выбирается элемент с индексом 0 при False или элемент с индексом 1 при True.
Вот несколько примеров:
Python:
# выражение
a1 = (5, 3)[5 < 3]
print('a1 =', a1)
# эквивалентно следующему
a2 = (5, 3)[False]
print('a2 =', a2)
# выражение
b1 = (5, 3)[5 > 3]
print('b1 =', b1)
# эквивалентно следующему
b2 = (5, 3)[True]
print('b2 =', b2)

# выражение (5, 3)[5 < 3] эквивалентно (5, 3)[False]
print('(5, 3)[5 < 3] = ', (5, 3)[5 < 3])
print('(5, 3)[False] =', (5, 3)[False])
print('Эквивалентно' if (5, 3)[5 < 3] == (5, 3)[False] == (5, 3)[0] == 5 else 'Не эквивалентно')

# выражение (5, 3)[5 > 3] эквивалентно (5, 3)[True]
print('(5, 3)[5 > 3] =', (5, 3)[5 > 3])
print('(5, 3)[True] =', (5, 3)[True])
print('Эквивалентно' if (5, 3)[5 > 3] == (5, 3)[True] == (5, 3)[1] == 3 else 'Не эквивалентно')
 
Последнее редактирование:
  • Мне нравится
Реакции: borntohack

borntohack

змееуст
Команда форума
Модератор
Апр 22, 2020
78
62
18
39
Москва, РФ
Код:
var = (если_условие_истинно, если_условие_ложно)[условие]
В этом примере значения нужно поменять местами, то есть должно быть так:
Код:
var = (если_условие_ложно, если_условие_истинно)[условие]
Эта конструкция работает так: сначала идет проверка условия (в результате получается True или False), потом результат проверки служит индексом для кортежа в начале выражения. То есть из кортежа выбирается элемент с индексом 0 при False или элемент с индексом 1 при True.
Вот несколько примеров:
Python:
# выражение
a1 = (5, 3)[5 < 3]
print('a1 =', a1)
# эквивалентно следующему
a2 = (5, 3)[False]
print('a2 =', a2)
# выражение
b1 = (5, 3)[5 > 3]
print('b1 =', b1)
# эквивалентно следующему
b2 = (5, 3)[True]
print('b2 =', b2)

# выражение (5, 3)[5 < 3] эквивалентно (5, 3)[False]
print('(5, 3)[5 < 3] = ', (5, 3)[5 < 3])
print('(5, 3)[False] =', (5, 3)[False])
print('Эквивалентно' if (5, 3)[5 < 3] == (5, 3)[False] == (5, 3)[0] == 5 else 'Не эквивалентно')

# выражение (5, 3)[5 > 3] эквивалентно (5, 3)[True]
print('(5, 3)[5 > 3] =', (5, 3)[5 > 3])
print('(5, 3)[True] =', (5, 3)[True])
print('Эквивалентно' if (5, 3)[5 > 3] == (5, 3)[True] == (5, 3)[1] == 3 else 'Не эквивалентно')

Полностью согласен. Видимо, нельзя мне много писать в один день))))) Исправил
 

God

Уже не совсем ламер.
Пользователь
Апр 11, 2020
91
14
8
истинно, то какое значение будет у a?
 

borntohack

змееуст
Команда форума
Модератор
Апр 22, 2020
78
62
18
39
Москва, РФ
истинно, то какое значение будет у a?

не совсем понял вопрос. Речь об этой строчке?
Таким образом сначала вычислится some_func(). Если оно истинно - то var = 3, если ложно, то var = 5 if some_func2() else 8 - уже знакомая нам обычная тернарная операция.
Если да, то значение var будет 3, если some_sunc() - истинно.
Если some_func() ложно - то вычисляется вложенная тернарная операция: var = 5 если some_func2() истинно, и 8 - если some_func2() ложно
 
  • Мне нравится
Реакции: God

Форум IT Специалистов