
Поговорим о логических операциях. Допустим у нас есть цепочка из or
:
if x() or y() or z(): print('bingo!')
Чтобы print
сработал, нужно, чтобы хотя бы один из трех вызовов давал бы True
(или приводился к True
). Что если x()
сразу вернет True
? Тогда, очевидно, все выражение будет равняться True
в любом случае и независимо от того, что будет в y()
и z()
. Если смысл их вычислять? Нет! Python и не вычисляет. Тем самым достигается некоторая оптимизация, которая называется short circuiting (или короткое замыкание).
Это хорошо, только, если в оставшихся логических выражениях нет побочных эффектов, потому они не будут выполнены, если вычисление логического выражение будет остановлено. Давайте проверим:
def check(b): print('check.') return b if True or check(True): print('ok.') # ok. if False or check(True): print('ok.') # check. ok.
В первом случае check
не работает, потому что первый операнд True
уже предопределит судьбу выражения. А во втором случае – сработает, потому первый операнд False
не дает определенности и нужно вычислить check()
.
Аналогично все с оператором and
: как только первый операнд в цепочке вернет False
, выполнение прекратиться.
if True and False and check(True): ... # не выполнится check
Встроенные функции all
и any
тоже используют короткое замыкание, то есть all
перестает проверять на первом False
, а any
– на первом True
.
all(check(i) for i in [1, 1, 0, 1, 1]) # выведет 3 check из 5 any(check(i) for i in [0, 1, 0, 0, 0]) # выведет 2 check из 5
Эту особенность стоит помнить. Лично я сталкивался с алгоритмом, где было что-то вроде:
while step(x, y, True) or step(x, y, False): ...
По задумке оба step
должны выполнятся на каждой итерации, но из-за короткого замыкания второй из них иногда не выполнялся; алгоритм работал неверно.
Что если не нужно такое поведение?
Оказывается, что можно применять побитовые операторы «или» и «и» в логических выражениях, при этом каждый операнд будет вычисляться в любом случае. Цепочка вычисления не прервется, даже если результат уже очевиден.
def check(b): print('check.') return b check(False) & check(False) # & – битовое и check(True) | check(False) # | - битовое или
В этом случае оба check
сработают!
❗Внимание: есть подводные камни. Этот прием работает корректно только с булевыми типами! Если мы подставим целые числа, то результат может быть не тот, что ожидается. Яркий пример – это числа 1 и 2:
>>> bool(1 and 2) True >>> bool(1 & 2) False >>> 1 & 2 0
Поэтому, в логическом выражении, если тип операнда не булев, то его нужно привести. Недавний пример должен быть переписан так:
while bool(step(x, y, True)) | bool(step(x, y, False)): ...
Второй подводный камень: приоритет операторов |
и &
гораздо выше, чем у not
, and
и or
. Так что, если миксуем их, то всегда ставим скобки:
>>> not False or True True >>> not False | True False >>> (not False) | True True
Не подумайте, что я призываю использовать побитовые операции вместо логических. Но в редких случаях это может быть оправдано.
Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈