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