Давайте начнем с простого вопроса. Что выведет этот код?
print(5 or 0)
Многие ответят: True
. Кажется, что это очевидно. 5
— это истина, 0
— ложь, True or False
всегда будет True
. Логично?
Логично. Но неверно.
На самом деле, Python выведет 5
.
Если этот результат вас удивил, поздравляю — эта статья для вас. А если не удивил, останьтесь — мы дойдем до трюков, которые заставят вас уважительно кивнуть.
Мы погрузимся в механику логических операторов, которую многие упускают. Разберем, как это знание позволяет писать элегантный код, который новички принимают за магию, и как не попасть в ловушки, которые поджидают даже опытных программистов.
Прежде чем мы вскроем механику or
и and
, нужно усвоить одно ключевое понятие: «истинность» любого объекта в Python. Каждый объект, будь то число, строка, список или None
, можно оценить с точки зрения его «правдивости».
Falsy (ложные) значения: Это объекты, которые Python считает эквивалентом False
в логическом контексте. Их немного, и их легко запомнить:
None
False
0
, 0.0
, 0j
, Decimal(0)
)''
, ()
, []
, {}
, set()
, range(0)
)Truthy (истинные) значения: Это абсолютно все остальные объекты. Любое ненулевое число, любая непустая строка, любой непустой список — все они «истинны».
Проверить это легко с помощью встроенной функции bool()
:
print(f"Число 5: {bool(5)}") # -> True
print(f"Число 0: {bool(0)}") # -> False
print(f"Строка 'hello': {bool('hello')}") # -> True
print(f"Пустая строка: {bool('')}") # -> False
print(f"Пустой список: {bool([])}") # -> False
Это основа, на которой строится вся логика. Когда вы пишете if my_list:
, Python на самом деле за кулисами проверяет if bool(my_list):
.
or
и and
возвращают не булевы значенияА вот и главный твист, который всё меняет. Вопреки интуиции, операторы or
и and
не возвращают True
или False
. Они всегда возвращают один из своих операндов (то, что стоит слева или справа от оператора).
Этот механизм называется короткозамкнутыми вычислениями (short-circuit evaluation). Python ленив в хорошем смысле слова: он вычисляет ровно столько, сколько нужно для определения результата.
or
Правило для or
: «Верни первый истинный (truthy) операнд, который встретишь, или последний, если все ложные».
Python смотрит на левый операнд.
and
Правило для and
: «Верни первый ложный (falsy) операнд, который встретишь, или последний, если все истинные».
Логика обратная, но принцип тот же:
[!NOTE] Краткая памятка
x or y
: Еслиx
истинно, тоx
, иначеy
.x and y
: Еслиx
ложно, тоx
, иначеy
.
Давайте докажем, что Python действительно "ленится" и не вычисляет правую часть выражения, если результат уже известен.
Для этого мы используем код, который гарантированно вызовет ошибку: int("hello")
.
# Тестируем `or`
# Левый операнд `5` - истинный. Python видит его и сразу возвращает.
# До `int("hello")` дело даже не доходит!
result = 5 or int("hello")
print(f"Результат: {result}") # -> Результат: 5 (никакой ошибки!)
# А теперь поменяем их местами
# Левый операнд `0` - ложный. Python вынужден пойти дальше.
# Он пытается вычислить `int("hello")` и... падает с ошибкой.
try:
result = 0 or int("hello")
except ValueError as e:
print(f"Случилась ошибка: {e}") # -> Случилась ошибка: invalid literal for int()
Это и есть short-circuit. Python сэкономил время, не выполняя заведомо ненужную операцию.
То же самое происходит и с and
:
# Тестируем `and`
# Левый операнд `0` - ложный. Python видит его и сразу возвращает.
# До `int("hello")` дело опять не доходит.
result = 0 and int("hello")
print(f"Результат: {result}") # -> Результат: 0 (никакой ошибки!)
Понимание этой механики — не просто академический интерес. Это ключ к написанию более чистого, короткого и выразительного кода.
Представьте, вам нужно запросить у пользователя имя и, если он ничего не ввел, присвоить значение «Аноним».
Стандартный, многословный способ:
name = input("Введите имя: ")
if not name: # Проверяем, не является ли строка пустой (falsy)
name = "Аноним"
print(f"Привет, {name}!")
Элегантный способ с or
:
name = input("Введите имя: ") or "Аноним"
print(f"Привет, {name}!")
Если пользователь вводит имя, input()
возвращает непустую строку (которая truthy
). Оператор or
видит это и немедленно возвращает ее. Если пользователь просто нажимает Enter, input()
возвращает пустую строку ''
(которая falsy
). Оператор or
, видя ложный операнд слева, возвращает то, что справа — "Аноним"
. Код стал короче, а намерение — яснее.
А теперь — настоящая магия. Достаточно распространённая задачка: на вход подаётся целое число, далее нужно было сложить все его цифры. Если получается не однозначное число, то снова сложить его цифры, и так далее пока не получим однозначное.
Пример: digital_root(493193)
4 + 9 + 3 + 1 + 9 + 3 = 29
2 + 9 = 11
1 + 1 = 2
Результат: 2
.Стандартное решение в лоб выглядело бы как-то так, с использованием цикла или рекурсии:
def digital_root_naive(n):
while n >= 10:
n = sum(int(digit) for digit in str(n))
return n
Работает. Но скучно и многословно.
А теперь, зная, как работают or
и and
, можно использовать известное математическое свойство (связь результата с остатком от деления на 9) и написать вот это:
def digital_root_pro(n):
return n % 9 or n and 9
Эта одна строчка делает то же самое. Давайте разберем, как, применяя наши знания шаг за шагом.
Деконструкция магии:
Случай 1: Число n
не кратно 9 (и не равно 0). Например, n = 493193
.
n % 9
равно 2
. Это ненулевое число, то есть truthy
.(n % 9) or (n and 9)
первая часть (2
) истинна.2
и игнорирует все, что после or
. Результат верный.Случай 2: Число n
кратно 9 (и не равно 0). Например, n = 18
.
n % 9
равно 0
. Это falsy
.(n % 9) or (n and 9)
первая часть ложна. Python вынужден вычислить вторую часть: n and 9
.n and 9
(что эквивалентно 18 and 9
): левый операнд 18
— истинный. Значит, оператор and
вернет правый операнд, то есть 9
.0 or 9
. Левый операнд ложный, значит or
вернет правый — 9
. Результат верный. Цифровой корень числа, кратного 9, — это 9.Это элегантное применение фундаментальных знаний о языке и математике.
Кстати, есть еще одна область, где неявное использование and
может подложить свинью. Смотрим на такую цепочку сравнений:
x = 15
print(10 < x < 20) # -> True
Дело в том, что Python обрабатывает цепочки сравнений не так, как кажется. Запись a < b < c
— это синтаксический сахар для a < b and b < c
. В нашем случае это (10 < x) and (x < 20)
. Проблемы начинаются, когда мы пытаемся сравнить результат с булевым значением напрямую и неочевидным образом:
# Казалось бы, должно быть True
print(10 < x < 20 == True) # -> False. Что?!
Почему False
? Потому что Python обрабатывает это как одну длинную цепочку сравнений, связанных неявным and
: (10 < x) and (x < 20) and (20 == True)
.
10 < 15
-> True
15 < 20
-> True
20 == True
-> False
(так как True
в числовом контексте это 1
, а 20 != 1
)Вся цепочка с and
становится ложной из-за последнего, неожиданного сравнения.
[!WARNING] Осторожно, грабли! Такое поведение может привести к трудноуловимым багам, когда часть логики оказывается неверной из-за неправильно составленной цепочки.
Правильно делать так, явно отделяя логический блок скобками, чтобы сначала вычислилось всё сравнение, а потом его результат сравнивался с True
:
print((10 < x < 20) == True) # -> True
[!WARNING] А лучше никогда не сравнивайте результат логического выражения с
True
илиFalse
напрямую (if condition == True:
). Это избыточно и может привести к ошибкам, как в примере выше. Правильный питоничный способ:if condition:
илиif not condition:
.
Ваша поддержка — это энергия для новых статей и проектов. Спасибо, что читаете!
True/False
для or/and
: Думайте о них как об операторах, которые возвращают один из операндов. Это изменит ваш подход к написанию логических выражений.and
: В цепочках сравнений всегда держите в голове, как Python их интерпретирует, и используйте скобки для группировки, чтобы избежать сюрпризов.Эти знания не только помогут вам решать заковыристые квизы, но и позволят писать более лаконичный, эффективный и питоничный код.