Углубление в логические операторы Python

/notes/deep_bool_oper_python/feature.png

Конспект посвящён скрытым особенностям и неочевидным деталям логических операторов в Python

Truthy и falsy объекты

Falsy — объекты, которые могут быть оценены как значение False. К ним относятся следующие объекты:

  • Значение False
  • Значение None
  • Числовые нули: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
  • Пустые последовательности и коллекции: b'', bytearray(b''), '', [], (), {}, set(), range(0)

Truthy — объекты, которые могут быть оценены как значение True. К ним относятся все объекты, не относящиеся к falsy.

Чтобы представить такие объекты в булевом виде, можно использовать функцию bool().


Операторы and и or

Операторы and и or не приводят свои результаты принудительно к значениям True или False, а возвращают один из своих операндов. Такой подход позволяет использовать эти операторы в более общих операциях, а не только в булевых.

Для начала посмотрим на оператор or:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
print(None or 0)
print(0 or 5)
print('beegeek' or None)
print([1, 2, 3] or [6, 9])

print(1 or 'beegeek' or None)
print(0.0 or 'habr' or {'one': 1})
print(0 or '' or [6, 9])
print(0 or '' or [])
print(0 or '' or [] or {})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Вывод:
0
5
beegeek
[1, 2, 3]
1
habr
[6, 9]
[]
{}

Как можно видеть, оператор or оценивает каждый свой операнд как truthy или falsy объект, однако возвращает не значение True или False, а сам объект по определенному правилу — первый truthy объект либо последний объект, если truthy объекты в логическом выражении не найдены.

Подобным образом с помощью or можно определять значение по умолчанию, к примеру:

1
2
greet = input("Ваше имя >> ") or "noname"
print(f"Привет, {greet}!")

Если пользователь ничего не передаст в функцию input(), в переменную greet попадёт строка "noname".

Перейдём к оператору and:

1
2
3
4
5
6
7
8
print(None and 10)
print(5 and 0.0)
print('beegeek' and {})
print([1, 2, 3] and [6, 9])

print(1 and 'beegeek' and None)
print('habr' and 0 and {'one': 1})
print(10 and [6, 9] and [])
1
2
3
4
5
6
7
8
# Вывод:
None
0.0
{}
[6, 9]
None
0
[]

Оператор and возвращает первый falsy объект либо последний объект, если falsy объекты в логическом выражении не найдены.


Short-circuit evaluation

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

  • Если левый операнд оператора or является truthy объектом, то общим результатом логического выражения является True, независимо от значения правого операнда.
  • Если левый операнд оператора and является falsy объектом, то общим результатом логического выражения является False, независимо от значения правого операнда.

Данный механизм называется вычислением по короткой схеме (short-circuit evaluation) и используется интерпретатором для оптимизации вычислений. Рассмотрим пример:

1
2
3
4
5
6
7
8
def f():
    print('bee')
    return 3
  
if True or f():
    print('geek')

# Вывод: geek

Левым операндом оператора or является truthy объект, значит, для вычисления общего результата логического выражения нет необходимости вычислять правый операнд, то есть вызывать функцию f(). Поскольку вызова функции не происходит, в выводе отсутствует строка bee.

И наоборот, если заменить or на and, в выводе будут обе строки.


Приоритет логических операторов

Приоритет указан от наивысшего к наименьшему:

  1. not
  2. and
  3. or

По отношению к другим операторам Python (за исключением оператора присваивания =) логические операторы имеют самый низкий приоритет.

1
2
3
a = 5
b = 7
print(not a == b)  # True

В этом коде сначала сравниваются значения переменных, затем применяется оператор not.

Стоит упомянуть, что подобная запись будет вызывать исключение SyntaxError:

1
2
3
a = 5
b = 7
print(a == not b)

Рассмотрим ещё один пример:

1
2
3
print(not 1 == 2 or 3 == 3 and 5 == 6)

# Вывод: True

Согласно приоритету операторов в первую очередь вычисляются выражения 1 == 2, 3 == 3 и 5 == 6, в результате чего исходное выражение принимает вид not False or True and False. Далее выполняется оператор not, возвращая значение True, после него — оператор and, возвращая значение False. Выражение принимает вид True or False. Последним выполняется оператор or, возвращая общий результат выражения — значение True.
P.S. На самом деле после вычисления not 1 == 2 оператор or сразу вернёт True, не вычисляя правую часть.


Цепочки сравнений

Как известно, сравнивать объекты в Python можно в укороченной форме, например вместо a < b and b < c писать a < b < c

Поскольку оператор and реализует вычисление по короткой схеме, все сравнения, которые располагаются правее сравнения, вернувшего ложный результат, не выполняются, и их операнды не вычисляются:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def f():
    print('bee')
    return 3

if 5 < 1 < f():
    print('geek')
else:
    print('beegeek')

# Вывод: beegeek

Пояснение: выражение 5 < 1 < f() эквивалентно выражению 5 < 1 and 1 < f(). Сравнение 5 < 1 возвращает False. В результате сравнение 1 < f() не выполняется, и функция f() не вызывается.

Тем не менее между сокращенным и расширенным вариантами записи цепочек сравнений существует важное отличие. В сокращенном выражении значение, стоящее по середине (между операторами сравнения), будет вычислено один раз (если его вообще нужно вычислять), а в развёрнутом выражении — два раза:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def f():
    print('bee')
    return 3

if 1 < f() < 5:
    print('geek')

print()

if 1 < f() and f() < 5:
    print('geek')
1
2
3
4
5
6
7
# Вывод:
bee
geek

bee
bee
geek

Помимо операторов сравнения, в цепочку операторов могут объединяться и другие операторы Python.

1
2
3
4
5
6
lst = [1, 2, 3]
num = 2

print(num in lst == True)

# Вывод: False

Данное выражение на самом деле эквивалентно выражению num in lst and lst == True, которое, в свою очередь, эквивалентно выражению True and False. Следовательно, результатом данной цепочки операторов является значение False.

Рассмотрим ещё один пример:

1
2
3
4
5
6
a = 5
b = 5
c = 10

print(a < c is True)        
print(a == b in [True])
1
2
3
# Вывод:
False
False

Основной источник: https://habr.com/ru/articles/824170/

Доп. источники:

Поддержать автора
NoisyCake cloudtipscloudtips
0%