Логические операции играют важную роль в программировании. Они используются для создания условных конструкций и составления сложных алгоритмов. В Python для выполнения логических операций используются логические операторы:
-
not
— логическое отрицание -
and
— логическое умножение -
or
— логическое сложение
В этой статье мы поговорим о неочевидных деталях и скрытых особенностях работы логических операторов в Python.
Таблицы истинности логических операторов
Мы привыкли к тому, что обычно в языках программирования логические операторы возвращают значения True
или False
согласно своим таблицам истинности.
Таблица истинности оператора not
:
a |
not a |
---|---|
|
|
|
|
Таблица истинности оператора and
:
a |
b |
a and b |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Таблица истинности оператора or
:
a |
b |
a or b |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Когда операндами логических операторов являются объекты True
и False
, работа логических операторов в Python также соответствует данным таблицам истинности.
Приведенный ниже код:
print(not True) print(not False) print(False and True) print(True and True) print(False or True) print(False or False)
выводит:
False True False True True False
Однако Python не ограничивает нас только значениями True
и False
в качестве операндов логических операторов. Операндами операторов not
, and
и or
могут быть объекты любых других типов данных.
Понятия truthy и falsy
Одной из важных особенностей языка Python является концепция truthy
и falsy
объектов. Любой объект в Python может быть оценен как True
или False
. При этом объекты, которые оцениваются как True
, называются truthy
объектами, а объекты, которые оцениваются как False
— falsy
объектами.
К встроенным falsy
объектам относятся:
-
значение
False
-
значение
None
-
нули числовых типов данных:
0
,0.0
,0j
,Decimal(0)
,Fraction(0, 1)
-
пустые последовательности и коллекции:
''
,()
,[]
,{}
,set()
,range(0)
Другие объекты встроенных типов данных относятся к truthy
объектам. Экземпляры пользовательских классов по умолчанию также являются truthy
объектами.
Чтобы привести объекты к значению True
или False
, используется встроенная функция bool()
.
Приведенный ниже код:
# falsy объекты print(bool(False)) print(bool(None)) print(bool(0)) print(bool(0.0)) print(bool([])) print(bool('')) print(bool({})) #truthy объекты print(bool(True)) print(bool(123)) print(bool(69.96)) print(bool('beegeek')) print(bool([4, 8, 15, 16, 23, 42])) print(bool({1, 2, 3}))
выводит:
False False False False False False False True True True True True True
Концепция truthy
и falsy
объектов в Python позволяет работать с условным оператором в более простой манере.
Например, приведенный ниже код:
if len(data) > 0: ... if value == True: ... if value == False: ...
можно переписать в виде:
if data: ... if value: ... if not value: ...
На картинке ниже представлены примеры упрощенной записи условного оператора с различными объектами Python согласно концепции truthy
и falsy
объектов:
Оператор not
Как мы уже знаем, операндом оператора not
может быть объект любого типа. Если операнд отличен от значений True
и False
, он оценивается в соответствии с концепцией truthy
и falsy
объектов. При этом результатом работы оператора not
всегда является значение True
или False
.
Приведенный ниже код:
print(not False) print(not None) print(not 0) print(not 0.0) print(not []) print(not '') print(not {})
выводит:
True True True True True True True
Приведенный ниже код:
print(not True) print(not 123) print(not 69.96) print(not 'beegeek') print(not [4, 8, 15, 16, 23, 42]) print(not {1, 2, 3})
выводит:
False False False False False False
Операторы and и or
Операндами операторов and
и or
, как и в случае с not
, могут быть объекты любых типов данных. По аналогии с оператором not
можно предположить, что результатом работы логических операторов and
и or
также является значение True
или False
. Однако на самом деле данные операторы возвращают один из своих операндов. Какой именно — зависит от самого оператора.
Приведенный ниже код:
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 {})
выводит:
0 5 beegeek [1, 2, 3] 1 habr [6, 9] [] {}
Как мы видим, оператор or
оценивает каждый свой операнд как truthy
или falsy
объект, однако возвращает не значение True
или False
, а сам объект по определенному правилу — первый truthy
объект либо последний объект, если truthy
объекты в логическом выражении не найдены.
Аналогично дело обстоит с оператором and
.
Приведенный ниже код:
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 [])
выводит:
None 0.0 {} [6, 9] None 0 []
Оператор and
возвращает первый falsy
объект либо последний объект, если falsy
объекты в логическом выражении не найдены.
Логические операторы ленивы
Логические операторы в Python являются ленивыми. Это означает, что возвращаемый операнд вычисляется путем оценки истинности всех операндов слева направо до тех пор, пока это остается актуальным:
-
если левый операнд оператора
or
являетсяtruthy
объектом, то общим результатом логического выражения являетсяTrue
, независимо от значения правого операнда -
если левый операнд оператора
and
являетсяfalsy
объектом, то общим результатом логического выражения являетсяFalse
, независимо от значения правого операнда
Данный механизм называется вычислением по короткой схеме (short-circuit evaluation) и используется интерпретатором для оптимизации вычислений. Рассмотрим наглядный пример, демонстрирующий данное поведение.
Приведенный ниже код:
def f(): print('bee') return 3 if True or f(): print('geek')
выводит:
geek
Левым операндом оператора or
является truthy
объект (значение True
), значит, для вычисления общего результата логического выражения нет необходимости вычислять правый операнд, то есть вызывать функцию f()
. Поскольку вызова функции не происходит, в выводе отсутствует строка bee
. Общим результатом логического выражения является значение True
, а значит, выполняются инструкции блока кода условного оператора, и в выводе мы видим строку geek
.
Напротив, приведенный ниже код:
def f(): print('bee') return 3 if True and f(): print('geek')
выводит:
bee geek
Левым операндом оператора and
является truthy
объект (значение True
), значит, для вычисления общего результата логического выражения необходимо вычислить и правый операнд, то есть вызвать функцию f()
. В результате вызова выполняются инструкции из тела функции, поэтому в выводе мы видим строку bee
. Функция возвращает число 3
, которое также является truthy
объектом. Таким образом, общим результатом логического выражения является число 3
, а значит, выполняются инструкции блока кода условного оператора, и в выводе мы видим строку geek
.
Приоритет логических операторов
Важно помнить о приоритете логических операторов. Ниже логические операторы представлены в порядке уменьшения приоритета (сверху вниз):
-
not
-
and
-
or
Согласно приоритету логических операторов приведенный ниже код:
a = 0 b = 7 c = 10 print(not a and b or not c) # 7
эквивалентен следующему:
a = 0 b = 7 c = 10 print(((not a) and b) or (not c)) # 7
По отношению к другим операторам Python (за исключением оператора присваивания =
) логические операторы имеют самый низкий приоритет.
Например, приведенный ниже код:
a = 5 b = 7 print(not a == b) # True
эквивалентен следующему:
a = 5 b = 7 print(not (a == b)) # True
Отметим, что запись вида:
a = 5 b = 7 print(a == not b)
недопустима и приводит к возбуждению исключения SyntaxError
.
Для большей наглядности рассмотрим подробно другой пример.
Приведенный ниже код:
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
.
Цепочки сравнений
Иногда нам требуется объединить операции сравнения в цепочку сравнений.
Рассмотрим программный код:
a = 5 b = 10 c = 15 print(a < b < c) # True print(a < b and b < c) # True
Выражения a < b < c
и a < b and b < c
представляют собой сокращенный и расширенный варианты записи цепочки сравнений и являются эквивалентными, так как на самом деле для объединения сравнений в сокращенном выражении a < b < c
оператор and
используется неявно.
Поскольку оператор and
реализует вычисление по короткой схеме, все сравнения, которые располагаются правее сравнения, вернувшего ложный результат, не выполняются, и их операнды не вычисляются.
Приведенный ниже код:
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()
не вызывается.
Тем не менее между сокращенным и расширенным вариантами записи цепочек сравнений существует важное отличие.
Приведенный ниже код:
def f(): print('bee') return 3 if 1 < f() < 5: print('geek')
выводит:
bee geek
в то время как приведенный ниже код:
def f(): print('bee') return 3 if 1 < f() and f() < 5: print('geek')
выводит:
bee bee geek
Как мы видим, в сокращенном выражении 1 < f() < 5
функция f()
вызывается только один раз, а в расширенном выражении 1 < f() and f() < 5
— два раза. Данную особенность важно учитывать, когда операнд, участвующий в сравнении, возвращает непостоянный результат.
Например, приведенный ниже код:
from random import randint def f(): x = randint(1, 7) print(x) return x print(1 < f() < 5) print(1 < f() and f() < 5)
выводит (результат может отличаться):
4 True 7 5 False
В примере выше в сокращенной записи функция f()
вызывается один раз и возвращает значение 4
. Однако в расширенной записи функция f()
вызывается дважды, возвращая разные значения (7
и 5
). Поэтому в данном случае выражения 1 < f() < 5
и 1 < f() and f() < 5
не являются эквивалентными.
Помимо операторов сравнения, в цепочку операторов могут объединяться и другие операторы Python. При этом в некоторых случаях мы можем столкнуться с неожиданным поведением программы из-за аналогичного неявного вызова оператора and
.
Например, приведенный ниже код:
lst = [1, 2, 3] num = 2 print(num in lst == True)
выводит:
False
Можно подумать, что результатом выражения num in lst == True
должно быть значение True
, однако это не так. Дело в том, что данное выражение на самом деле эквивалентно выражению num in lst and lst == True
, которое, в свою очередь, эквивалентно выражению True and False
. Следовательно, результатом данной цепочки операторов является значение False
.
Рассмотрим еще два примера с неожиданным поведением.
Приведенный ниже код:
a = 5 b = 5 c = 10 print(a < c is True) print(a == b in [True])
эквивалентен коду:
a = 5 b = 5 c = 10 print(a < c and c is True) print(a == b and b in [True])
и выводит:
False False
Подведем итоги
Понимание особенностей работы логических операторов критически важно для программирования, поскольку логические выражения используются практически в любой компьютерной программе.
Логические операторы and
и or
являются ленивыми операторами. Они возвращают один из своих операндов, реализуя вычисления по короткой схеме. В цепочках операторов оператор and
может использоваться неявно. Об этом всегда стоит помнить при объединении различных операторов в одно выражение.
Присоединяйтесь к нашему телеграм-каналу, будет интересно и познавательно!
❤️ Happy Pythoning! ?
ссылка на оригинал статьи https://habr.com/ru/articles/824170/
Добавить комментарий