Истинные значения в Python

Истинные значения — это значения, которые оценивается как True при использовании в булевом контексте. Аналогично, ложные значения — это значения, которые оцениваются как False. Это полезная фича Python и некоторых других языков.

Применение этого функционала языка позволяет писать лаконичный и незагроможденный код. Однако с использованием истинных и ложных значений связаны некоторые ловушки, в которые могут угодить новички.

Хотите скачать книги по Python в 2 клика? Тогда вам в наш телеграм канал PythonBooks 

Истинные и ложные значения в Python

Следующие значения являются ложными:

  • нулевое число (целое, вещественное или комплексное)
  • строка, список, кортеж, словарь или множество нулевой длины
  • объект None
  • объект False

А это истинные значения:

  • ненулевое число (целое, вещественное или комплексное)
  • строка, список, кортеж, словарь или набор ненулевой длины
  • объект True
  • почти все другие объекты, которые не входят в список ложных значений.

Использование истинности значений

Вот несколько примеров того, как истинные значения могут сделать ваш код немного понятнее. Рассмотрим следующий код:

n = 1
if n != 0:
    print("n is not zero")

Этот код можно сделать немного аккуратнее, применив истинные значения. Если значение n не равно нулю, оно оценивается как True, поэтому предложение if будет выполнено:

n = 1
if n:
    print("n is not zero")

Вот код, который выводит список только в том случае, если он не пуст:

k = [...]
if len(k)!=0:
    print(k)

Этот код также можно упорядочить, используя истинность значений. Если список не пуст, он оценивается как True и будет напечатан:

k = [...]
if k:
    print(k)

Наконец, этот код предложит пользователю ввести свое имя, но только если переменная name пуста:

name = ...
if len(name)==0:
    name = input("What is your name?")

Этот код делает то же самое, используя истинность значений:

name = ...
name = name or input("What is your name?")

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

Сначала код оценивает значение name. Если name истинно (т.е. строка не пустая), то выражение с or будет истинным. Оператор or оценивает операнды слева направо, и если первый — True, результат возвращается немедленно.

От редакции Pythonist: чуть подробнее о логических операторах читайте в статье «Операторы в Python».

Python не нужно вызывать функцию input(), потому что он уже знает, что результат будет истинным. Более того, Python гарантированно не будет оценивать второе выражение, если первое истинно. Он просто сразу вернет имеющееся истинное значение, т.е. начальное значение переменной name.

А если name изначально является пустой строкой? В этом случае Python не знает, будет ли все выражение or истинным или ложным. Чтобы определиться, ему придется оценить второй операнд. Это означает, что если переменная name пуста, нужно вызвать функцию input(). Пользователю будет предложено ввести свое имя, и все, что он введет, будет возвращено как истинное значение и присвоено переменной name.

К истинности значений, возможно, придется немного привыкнуть, если вы не сталкивались с этой концепцией раньше, но в целом это хорошая вещь. Истинные значения делают ваш код менее загроможденным и часто позволяют избежать ненужных сравнений или вызовов len.

От редакции Pythonist: читайте также «Функция len() в Pyhton».

Темная сторона

Рассмотрим этот код:

import math
def checked_sqrt(x):
    try:
        return math.sqrt(x)
    except ValueError:
        return None
answer = checked_sqrt(4)
if answer:
    print(answer)
else:
    print("Error calculating square root")

Мы определяем функцию checked_sqrt, которая вызывает функцию math.sqrt для вычисления квадратного корня.

Однако функция math.sqrt выдаст ошибку ValueError, если x будет отрицательным, поскольку отрицательные числа не имеют квадратного корня. Наша функция обрабатывает этот случай, возвращая None, чтобы указать, что значение не может быть вычислено.

Затем мы вызываем checked_sqrt и проверяем ответ. Если мы вызываем функцию со значением 4, функция вычислит квадратный корень как 2.0, что является истинным значением, поэтому оператор if выведет ответ.

Если мы вызовем checked_sqrt со значением -4, произойдет исключение, и функция вернет None. Это ложное значение, поэтому оператор if выведет сообщение об ошибке.

Все это нормально. Но что если мы передадим значение 0? math.sqrt вычислит квадратный корень, который, конечно же, равен 0.0. Поскольку исключения не было, checked_sqrt вернет значение 0.0.

Но когда мы доходим до оператора if, answer имеет значение 0.0, которое оценивается как ложное (просто строка None). Это означает, что наш код неправильно выведет сообщение об ошибке, несмотря на то, что квадратный корень был вычислен правильно.

Анализ

В чем здесь дело? Основная проблема заключается в том, что проверка истинности значения answer не может отличить случаи None и 0.0.

Если копнуть глубже, проблема заключается в том, что Python использует динамическую типизацию. Это значит, что функция checked_sqrt может возвращать разные типы данных (float или объект None) при разных обстоятельствах. Если бы мы попробовали выполнить этот код на Java, например, checked_sqrt была бы объявлена как возвращающая float, поэтому возврат null был бы невозможен (это привело бы к ошибке компиляции).

Это усугубляется неправильным направлением кода. Функция явно возвращает None, но тот факт, что она также может возвращать другое ложное значение 0.0, не очевиден. Беглый взгляд на код может убедить вас (ошибочно), что None — единственное возвращаемое значение, которое будет оценено как ложное.

Решение

Во-первых, стоит помнить, что во многих случаях это не будет проблемой. Допустим, у вас есть локальная переменная, которая объявлена со строковым значением. Если при использовании этой переменной в принципе не может случиться, что она будет содержать что-либо, кроме строкового значения, то совершенно нормально использовать ее истинность для проверки того, пуста ли она.

Это верно, если переменная принимает значение из функции, возвращающей только строковые значения. Так будет, если это значение является параметром этой функции, который должен быть только строковым. Для проверки пустоты строки значение должно быть строкой, поэтому допустимо использовать истинность его значения. Значение строки не должно быть None (это не предполагается), поэтому вы можете игнорировать такую возможность.

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

Таким образом, эта проблема касается только ситуаций, когда значение законно может иметь разные типы. Приведенный выше пример, где переменная может иметь допустимое значение None, является очень типичной.

Одно из решений очень простое. В таких случаях не используйте проверку на истинность. Вместо этого явно проверяйте, является ли значение None:

if answer is not None:
    print(answer)
else:
    print("Error calculating square root")

Решение простое, сложность заключается в том, чтобы приучить себя всегда помнить о проблемах такого рода.

Второе решение заключается в том, чтобы полностью избежать проблемы, не позволяя функции возвращать неоднозначный результат. Например:

  • Функция может всегда возвращать число, но это число может быть >=0, что означает валидный результат, или -1, что означает невалидный результат.
  • Функция может всегда возвращать кортеж. (True, value) будет означать, что value валидно. (False, value) укажет на отсутствие валидного результата, и value должно быть проигнорировано.
  • Аналогичным образом можно использовать опции.

Опциональный объект — это специальный объект, содержащий значение, которое может присутствовать или отсутствовать. Он включает методы для проверки наличия значения, получения значения, если оно есть, и другие полезные функции. По сути, это более чистая альтернатива неформальному протоколу value-or-none.

Итоги

Истинные значения — очень полезная функция Python, но в определенных обстоятельствах они могут создавать двусмысленность и ошибки. В этой статье было приведено несколько решений этой проблемы, которые можно применять в зависимости от конкретных обстоятельств.

Перевод статьи «The Hidden Traps of Python Truthy Values».