Давайте начнем с простого вопроса. Что выведет этот код?
print(5 or 0)
Многие ответят: True. Кажется, что это очевидно. 5 — это истина, 0 — ложь, True or False всегда будет True. Логично?
Логично. Но неверно.
На самом деле, Python выведет 5.
Если этот результат вас удивил, поздравляю — эта статья для вас. А если не удивил, останьтесь — мы дойдем до трюков, которые заставят вас уважительно кивнуть.
Мы погрузимся в механику логических операторов, которую многие упускают. Разберем, как это знание позволяет писать элегантный код, который новички принимают за магию, и как не попасть в ловушки, которые поджидают даже опытных программистов.
Прежде чем мы вскроем механику or и and, нужно усвоить одно ключевое понятие: «истинность» любого объекта в Python. Каждый объект, будь то число, строка, список или None, можно оценить с точки зрения его «правдивости».
Falsy (ложные) значения: Это объекты, которые Python считает эквивалентом False в логическом контексте. Их немного, и их легко запомнить:
NoneFalse0, 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 = 292 + 9 = 111 + 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 -> True15 < 20 -> True20 == 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 их интерпретирует, и используйте скобки для группировки, чтобы избежать сюрпризов.Эти знания не только помогут вам решать заковыристые квизы, но и позволят писать более лаконичный, эффективный и питоничный код.