Истина и ложь в Python

Перевод статьи “Telling The Truthy”.

Истина и ложь в Python – это не только булевы True и False. Я всегда говорю правду. Честно! Мне даже детям неловко врать.

«А Дед Мороз правда существует?»

«А как ты думаешь? Я никогда его не видел, но в новогоднее утро под елкой лежат подарки. Как, по-твоему, они туда попали?»

Как видите, в моем ответе нет ничего неправдивого.

А иногда преподаватели говорят ученикам неправду – не потому, что хотят обмануть или обмануть их, а потому, что правда слишком сложна. Более простую «неправду» легче объяснить.

Когда я преподаю на курсах для начинающих и ввожу оператор if или цикл while, я использую такие фразы, как:

«За if должно следовать что-то, что Python понимает как истину или ложь».

Конечно, проще сказать, что за if должно следовать True или False. Но это было бы неправдой. За ключевым словом if может следовать любой объект или выражение. Выражение оценивается в объект, а Python может «рассматривать любой объект как истинный или ложный», если использовать его в булевом контексте.

Истинность и ложность

Начнем со встроенной функции bool(), с помощью которой вы можете преобразовать любой объект в булево значение.

Мы склонны думать о bool() как о функции. Однако bool – это имя класса. Поэтому bool() – это конструктор, который создает экземпляр bool из своего аргумента. Но это всего лишь деталь, и она не важна для нашего обсуждения. Поэтому давайте двигаться дальше.

Чтобы убедиться в этом, рассмотрим несколько коротких примеров (прим. ред.: здесь и далее код выполняется в консоли):

if 5:
    print("Yay")
else:
    print("Nay")
   
# Yay
​

if 0:
    print("Yay")
else:
    print("Nay")
   
# Nay
​

bool(5)
# True


bool(0)
# False

Любое целое число, кроме 0, преобразуется в True, когда вы приводите его к булевым значениям с помощью функции bool(). Таким образом, каждое ненулевое целое число является истинным, а 0 – ложным. Мы можем распространить эту идею на все числовые типы, а не только на целые числа. Например, 0.0 является ложным, а любое другое значение типа float – истинным.

Когда Python нужно понять, истинен или ложен объект, он будет использовать значение, возвращаемое функцией bool().

Для объектов, имеющих длину (такие объекты называются размерными), объект является ложным, если его длина равна 0, и истинным для любой ненулевой длины:

if [1, 2]:
    print("Yay")
else:
    print("Nay")
   
# Yay
​

if []:
    print("Yay")
else:
    print("Nay")
   
# Nay
​

len([1, 2])
# 2

bool([1, 2])
# True
​

len([])
# 0

bool([])
# False

Вы можете попробовать это с другими размерными объектами, такими как строки, кортежи, словари и множества. Эти структуры данных являются ложными, если они пусты, и истинными, если непусты.

И есть еще один объект, который всегда ложный: None.

Истинность, ложность и определяемые пользователем классы

Как насчет других объектов, которые не являются числовыми и не имеют длины?

Давайте создадим простой класс и протестируем его:

class TestTruthiness:
    pass
​
test = TestTruthiness()
​
if test:
    print("Yay")
else:
    print("Nay")
   
# Yay
​

bool(test)
# True

Примечание редакции: о pass читайте в статье “Ключевое слово pass в Python”.

Каждый объект по умолчанию является истинным. Однако это поведение по умолчанию можно отменить. Вы можете определить истинность или ложность объекта, определив специальный метод __bool__() в определении класса:

class TestTruthiness:
    def __bool__(self):
        return False
​
​
test = TestTruthiness()
​
if test:
    print("Yay")
else:
    print("Nay")
   
# Nay
​

bool(test)
# False

Теперь у класса есть специальный метод __bool__(). В данном примере этот специальный метод всегда возвращает False. Теперь все экземпляры этого класса будут ложными.

Специальный метод __bool__(), конечно же, может содержать дополнительную логику:

class TestTruthiness:
    def __init__(self, person):
        self.person = person
    def __bool__(self):
        return self.person == "Stephen"
​
bool(TestTruthiness("Stephen"))
# True

bool(TestTruthiness("Jane"))
# False

Мы добавили специальный метод __init__() и атрибут данных .person. Теперь экземпляры этого класса будут истинными, если человека зовут «Stephen», и ложными для любого другого имени.

Но есть и другой способ контролировать истинность или ложность, не определяя специальный метод __bool__(). Вы можете определить метод __len__(), который делает объекты размерными – у них появляется длина:

class TestTruthiness:
    def __init__(self, person):
        self.person = person
    def __len__(self):
        return len(self.person)
​
bool(TestTruthiness("Stephen"))
# True

bool(TestTruthiness("Jane"))
# True

bool(TestTruthiness(""))
# False

Вы установили длину объекта TestTruthiness равной значению его атрибута данных .person – в данном случае это единственный атрибут, который есть у этого класса!

В этом примере объекты, представляющие людей с именами «Stephen» и «Jane», оба истинны. Все имена истинны. Но если вместо имени человека используется пустая строка, объект TestTruthiness становится ложным.

Некоторые другие «неожиданные» следствия истинности и ложности

Позвольте мне начать с примера:

first_list = []
second_list = [5, 10]
if first_list or second_list:
    print("At least one of the lists is not empty")
   
# At least one of the lists is not empty

Что в этом неожиданного? Мы видели, что first_list является ложным, поскольку это пустой список. А second_list является истинным, поскольку он не пуст. Вы используете выражение с or в предложении if, и поскольку один из списков является истинным, блок if выполняется.

Но что возвращает выражение first_list or second_list? Нет, оно не возвращает True:

first_list or second_list
# [5, 10]

Выражение начинается с first_list, что является ложным. Выражение or ищет хотя бы одно истинное значение. Поэтому оно переходит к объекту после ключевого слова or, second_list. И это значение истинно. Поэтому выражение or возвращает этот объект. Ему не нужно возвращать True, поскольку оно возвращает истинное значение, и этого достаточно!

А что, если истинностный объект находится перед ключевым словом or?

second_list or first_list
# [5, 10]

Вы получите тот же результат. Но в этом случае первый объект является истинным, а выражению or нужен только один истинный объект. Поэтому выражению or незачем смотреть, что идет после ключевого слова or.

Вам нужно доказательство?

second_list or int("hello")
# [5, 10]

И что здесь странного? Выражение после or должно вызывать ошибку:

int("hello")
# Traceback (most recent call last):
#   File "<input>", line 1, in <module>
# ValueError: invalid literal for int() with base 10: 'hello'

Строка “hello” не может быть преобразована в целое число. Но когда int("hello") используется после ключевого слова or в second_list или int("hello"), выражение после or так и не выполняется.

А что произойдет, если оба объекта в выражении or будут ложными?

0 or []
# []

Первый объект проверяется первым. Целое число 0 является ложным. Выражение ищет хотя бы один истинный объект. Поэтому проверяется второй объект. Пустой список [] также является ложным. Но выражению не нужно возвращать False. Вместо этого оно возвращает последний элемент, который является ложным. Этого вполне достаточно в любом булевом контексте Python.

Давайте подведем итог:

  • Выражение or возвращает первый объект, если он истинный.
  • Но если первый объект ложный, возвращается второй объект, каким бы он ни был. Если второй объект ложный, то и все выражение ложное. Но если второй объект истинный, то и все выражение истинное.

Аналогичную логику мы можем проследить и в случае с and. Выражение and требует, чтобы оба объекта были истинными. Поэтому, если первый объект ложен, выражение сразу же возвращает этот первый объект, не оценивая второй:

0 and 10
# 0
0 and int("hello")
# 0

Во втором из этих примеров ошибка не возникает, поскольку первый объект является ложным и возвращается сразу.

10 and int("hello")
# Traceback (most recent call last):
#   File "<input>", line 1, in <module>
# ValueError: invalid literal for int() with base 10: 'hello'

Но когда первый объект истинный, выражение and переходит к тому, что идет после ключевого слова and. Это приводит к ошибке.

Если в выражении and первый объект истинный, то всегда возвращается второй объект, независимо от того, истинный он или ложный:

10 and [2, 3]
# [2, 3]
10 and []
# []

Такое поведение с or и and известно как вычисление по короткой схеме. Теперь ваша очередь поиграть с выражениями or и and, а также с истинными и ложными объектами.

И некоторые факты о True и False

Эта статья не о типе данных Boolean и его двух экземплярах, True и False. Но в заключение позвольте мне рассказать немного фактов об этих объектах.

True + True
# 2

25 * False
# 0

some_dict = {1: "This is the integer 1", True: "This is the Boolean True"}
some_dict[1]
# 'This is the Boolean True'

Класс bool является подклассом int. Поэтому True равно 1, а False равно 0. Вот почему True + True равно 1 + 1 (и мне не нужно говорить вам, что это дает 2). Умножение на False – то же самое, что умножение на 0. А поскольку словари должны иметь уникальные ключи, при создании ключа True он отменяет значение предыдущего ключа 1, поскольку True равен 1.

Обратите внимание, что True и 1 – это не один и тот же объект, но они имеют одно и то же значение. True = 1 дает False, что говорит о том, что они не являются одним и тем же объектом. Но True == 1 дает True, что показывает, что они равны.

Заключительные слова

Насколько сложным может быть понятие true или false? Если расширить определение истинности и ложности, то можно обнаружить нечто большее, чем кажется на первый взгляд.

Настоящим я подтверждаю, что, насколько мне известно, в этой статье я предоставил вам правду, всю правду и ничего, кроме правды.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *