Давайте начнем с простого вопроса. Что выведет этот код?
print(5 or 0)
Многие ответят: True. Кажется, что это очевидно. 5 — это истина, 0 — ложь, True or False всегда будет True. Логично?
Логично. Но неверно.
На самом деле, Python выведет 5.
Если этот результат вас удивил, поздравляю — эта статья для вас. А если не удивил, останьтесь — мы дойдем до трюков, которые заставят вас уважительно кивнуть.
Мы погрузимся в механику логических операторов, которую многие упускают. Разберем, как это знание позволяет писать элегантный код, который новички принимают за магию, и как не попасть в ловушки, которые поджидают даже опытных программистов.
Часть 1: Truthy и Falsy. Что Python считает «правдой»?
Прежде чем мы вскроем механику or и and, нужно усвоить одно ключевое понятие: «истинность» любого объекта в Python. Каждый объект, будь то число, строка, список или None, можно оценить с точки зрения его «правдивости».
Falsy (ложные) значения: Это объекты, которые Python считает эквивалентом
Falseв логическом контексте. Их немного, и их легко запомнить:NoneFalse- Ноль для любых числовых типов (
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):.
Часть 2: Главный секрет. or и and возвращают не булевы значения
А вот и главный твист, который всё меняет. Вопреки интуиции, операторы or и and не возвращают True или False. Они всегда возвращают один из своих операндов (то, что стоит слева или справа от оператора).
Этот механизм называется короткозамкнутыми вычислениями (short-circuit evaluation). Python ленив в хорошем смысле слова: он вычисляет ровно столько, сколько нужно для определения результата.
Как на самом деле работает оператор or
Правило для or: «Верни первый истинный (truthy) операнд, который встретишь, или последний, если все ложные».
Python смотрит на левый операнд.
- Если он истинный (truthy), Python немедленно возвращает его значение и даже не смотрит на правый операнд. Зачем? Результат уже ясен.
- Если левый операнд ложный (falsy), Python возвращает значение правого операнда, каким бы оно ни было.
Как на самом деле работает оператор and
Правило для and: «Верни первый ложный (falsy) операнд, который встретишь, или последний, если все истинные».
Логика обратная, но принцип тот же:
- Если левый операнд ложный (falsy), Python немедленно возвращает его и игнорирует правый.
- Если левый операнд истинный (truthy), Python возвращает значение правого операнда.
[!NOTE] Краткая памятка
x or y: Еслиxистинно, тоx, иначеy.x and y: Еслиxложно, тоx, иначеy.
Часть 3: Доказательство лени. Short-circuit в действии
Давайте докажем, что 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 (никакой ошибки!)
Часть 4: От теории к практике. Элегантные паттерны использования
Понимание этой механики — не просто академический интерес. Это ключ к написанию более чистого, короткого и выразительного кода.
Паттерн №1: Значение по умолчанию (самый частый кейс)
Представьте, вам нужно запросить у пользователя имя и, если он ничего не ввел, присвоить значение «Аноним».
Стандартный, многословный способ:
name = input("Введите имя: ")
if not name: # Проверяем, не является ли строка пустой (falsy)
name = "Аноним"
print(f"Привет, {name}!")
Элегантный способ с or:
name = input("Введите имя: ") or "Аноним"
print(f"Привет, {name}!")
Если пользователь вводит имя, input() возвращает непустую строку (которая truthy). Оператор or видит это и немедленно возвращает ее. Если пользователь просто нажимает Enter, input() возвращает пустую строку '' (которая falsy). Оператор or, видя ложный операнд слева, возвращает то, что справа — "Аноним". Код стал короче, а намерение — яснее.
Паттерн №2: Решение алгоритмической задачки
А теперь — настоящая магия. Достаточно распространённая задачка: на вход подаётся целое число, далее нужно было сложить все его цифры. Если получается не однозначное число, то снова сложить его цифры, и так далее пока не получим однозначное.
Пример: 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) истинна. - Python немедленно возвращает
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.
Это элегантное применение фундаментальных знаний о языке и математике.
Часть 5: Бонусный уровень. Ловушка в цепочках сравнений
Кстати, есть еще одна область, где неявное использование 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: Думайте о них как об операторах, которые возвращают один из операндов. Это изменит ваш подход к написанию логических выражений. - Помните про Truthy и Falsy: Знание того, какие объекты Python считает истинными, а какие ложными — это база для понимания работы условных операторов и логики.
- Используйте short-circuit осознанно: Ленивость Python — ваш друг. Применяйте ее для создания чистого и идиоматичного кода, например, для присвоения значений по умолчанию.
- Опасайтесь неявных
and: В цепочках сравнений всегда держите в голове, как Python их интерпретирует, и используйте скобки для группировки, чтобы избежать сюрпризов.
Эти знания не только помогут вам решать заковыристые квизы, но и позволят писать более лаконичный, эффективный и питоничный код.