Подтвердить что ты не робот

Использование True, False и None в качестве возвращаемых значений в функциях Python

Я думаю, что я полностью понимаю это, но я просто хочу убедиться, потому что я продолжаю видеть, как люди говорят, что никогда не тестируют против True, False или None.

Они предполагают, что процедуры должны вызывать ошибку, а не возвращать False или None. Во всяком случае, у меня есть много ситуаций, когда я просто хочу узнать, установлен флаг или нет, поэтому моя функция возвращает True или False. Есть другие ситуации, когда у меня есть функция, возвращающая None, если не было никакого полезного результата. По моему мнению, ни то, ни другое не является проблематичным, если я понимаю, что никогда не должен использовать:

if foo == True
if foo == False
if foo == None

и вместо этого следует использовать:

if foo is True
if foo is False
if foo is None

поскольку True, False и None являются синглетонами и всегда будут оценивать то, что я ожидаю при использовании "is", а не "==". Я здесь не прав?

В том же духе, будет ли Pythonic модифицировать функции, которые иногда возвращают None, чтобы вместо них выдавать ошибку?

Скажем, у меня есть метод экземпляра get_attr(), который извлекает атрибут из некоторого файла. В случае, когда он обнаруживает, что запрошенный мной атрибут не существует, уместно ли возвращать None? Было бы лучше, чтобы они подняли ошибку и поймали ее позже?

4b9b3361

Ответ 1

Совет не в том, чтобы вы никогда не использовали True, False или None. Просто вы не должны использовать if x == True.

if x == True глупо, потому что == просто бинарный оператор! Он имеет возвращаемое значение True или False, в зависимости от того, равны ли его аргументы или нет. И if condition продолжится, если condition истинно. Поэтому, когда вы пишете if x == True, Python сначала оценит x == True, который станет True, если x было True, и False в противном случае, и затем продолжит работу, если результат этого равен true. Но если вы ожидаете, что x будет либо True, либо False, почему бы просто не использовать if x напрямую!

Аналогично, x == False обычно можно заменить на not x.

В некоторых случаях вы можете использовать x == True. Это связано с тем, что условие оператора if "оценивается в булевом контексте", чтобы увидеть, является ли оно "правдивым", а не проверять точно по True. Например, непустые строки, списки и словари считаются правильными с помощью оператора if, а также ненулевые числовые значения, но ни одно из них не равно True. Поэтому, если вы хотите проверить, является ли произвольное значение точно значением True, а не только является ли оно достоверным, когда вы используете if x == True. Но я почти никогда не вижу в этом смысла. Это настолько редко, что если вам когда-нибудь понадобится написать это, стоит добавить комментарий, чтобы будущие разработчики (включая, возможно, и вы сами) не просто решили, что == True излишен, и удалите его.


Использование x is True вместо этого на самом деле хуже. Никогда не следует использовать is с базовыми встроенными неизменяемыми типами, такими как логические значения (True, False), числа и строки. Причина в том, что для этих типов мы заботимся о значениях, а не о идентичности. == проверяет, что значения одинаковы для этих типов, а is всегда проверяет идентичность.

Тестировать тождества, а не значения, плохо, потому что реализация теоретически может создавать новые логические значения, а не искать уже существующие, что приводит к тому, что у вас есть два значения True, которые имеют одинаковое значение, но они хранятся в разных местах в памяти и имеют разные идентичностей. На практике я почти уверен, что True и False всегда повторно используются интерпретатором Python, так что этого не произойдет, но на самом деле это деталь реализации. Эта проблема все время сбивает людей с толку строками, потому что короткие строки и литеральные строки, которые появляются непосредственно в исходном тексте программы, перерабатываются Python, поэтому 'foo' is 'foo' всегда возвращает True. Но легко построить одну и ту же строку 2 разными способами и заставить Python присваивать им разные идентификаторы. Обратите внимание на следующее:

>>> stars1 = ''.join('*' for _ in xrange(100))
>>> stars2 = '*' * 100
>>> stars1 is stars2
False
>>> stars1 == stars2
True

ОБНОВЛЕНИЕ: Получается, что равенство Python в логических значениях немного неожиданно (по крайней мере для меня):

>>> True is 1
False
>>> True == 1
True
>>> True == 2
False
>>> False is 0
False
>>> False == 0
True
>>> False == 0.0
True

Обоснование этого, как объяснено в примечаниях, когда в Python 2.3.5 были введены bools, заключается в том, что старое поведение использования целых чисел 1 и 0 для представления True и False было хорошим, но мы нам просто нужно больше описательных имен для чисел, которые мы собирались представлять для истинных значений.

Одним из способов достижения этого было бы просто иметь True = 1 и False = 0 во встроенных программах; тогда 1 и True действительно будут неразличимы (в том числе is). Но это также означает, что функция, возвращающая True, будет показывать 1 в интерактивном интерпретаторе, поэтому вместо этого мы создали bool как подтип int. Единственное, что отличается от bool - это str и repr; Экземпляры bool по-прежнему имеют те же данные, что и экземпляры int, и сравнивают равенство таким же образом, поэтому True == 1.

Поэтому неправильно использовать x is True, когда x мог быть установлен каким-то кодом, который ожидает, что "Истина - это просто еще один способ написания 1", потому что есть много способов создать значения, которые равны True, но не иметь такой же личности, как это:

>>> a = 1L
>>> b = 1L
>>> c = 1
>>> d = 1.0
>>> a == True, b == True, c == True, d == True
(True, True, True, True)
>>> a is b, a is c, a is d, c is d
(False, False, False, False)

И неправильно использовать x == True, когда x может быть произвольным значением Python, и вам нужно только знать, является ли оно логическим значением True. Единственное, что у нас есть, это то, что использование x лучше всего, когда вы просто хотите проверить "правдивость". К счастью, это обычно все, что требуется, по крайней мере, в коде, который я пишу!

Более верный путь был бы x == True and type(x) is bool. Но это становится довольно многословным для довольно неясного случая. Это также выглядит не очень Pythonic, если выполнять явную проверку типов... но это действительно то, что вы делаете, когда пытаетесь проверить точно True, а не правдиво; способ типизации утки должен был бы принять истинные значения и позволить любому пользовательскому классу объявить себя правдивым.

Если вы имеете дело с этим чрезвычайно точным понятием истины, когда вы не только не рассматриваете непустые коллекции как истинные, но и не рассматриваете 1 как истинные, тогда, вероятно, хорошо использовать только x is True, потому что предположительно тогда вы знаете, что x не пришел из кода, который считает 1 истинным. Я не думаю, что существует какой-либо чистый Python способ придумать другой True, который живет по другому адресу памяти (хотя вы, вероятно, могли бы сделать это из C), так что это никогда не должно нарушаться, несмотря на то, что теоретически "неправильный" "что нужно сделать.

И раньше я думал, что логические выражения просты!

Конец редактирования


В случае None, однако, идиома должна использовать if x is None. Во многих случаях вы можете использовать if not x, потому что None является "ложным" значением для оператора if. Но лучше всего делать это только в том случае, если вы хотите обрабатывать все значения Falsey (числовые типы с нулевым значением, пустые коллекции и None) одинаково. Если вы имеете дело со значением, которое является каким-то другим возможным значением или None, чтобы указать "нет значения" (например, когда функция возвращает None в случае сбоя), то это намного лучше использовать if x is None, чтобы вы случайно не предположили, что функция завершилась с ошибкой, когда она только что возвратила пустой список или число 0.

Мои аргументы в пользу использования == вместо is для типов неизменяемых значений предполагают, что вы должны использовать if x == None вместо if x is None. Однако в случае None Python явно гарантирует, что во всей вселенной есть ровно один None, а обычный идиоматический код Python использует is.


Относительно того, возвращать None или вызывать исключение, это зависит от контекста.

Для чего-то вроде вашего примера get_attr я бы ожидал, что оно вызовет исключение, потому что я буду называть его как do_something_with(get_attr(file)). Обычное ожидание вызывающих абонентов состоит в том, что они получат значение атрибута, а получение им None и предположения, что это значение атрибута, представляет собой гораздо худшую опасность, чем забывание обрабатывать исключение, когда вы действительно можете продолжить, если атрибут может не может быть найдено. Кроме того, возврат None для обозначения сбоя означает, что None не является допустимым значением для атрибута. Это может быть проблемой в некоторых случаях.

Для воображаемой функции, такой как see_if_matching_file_exists, для которой мы предоставляем шаблон, и она проверяет несколько мест, чтобы увидеть, есть ли совпадение, она может вернуть совпадение, если найдет одну, или None, если не найдет. Но в качестве альтернативы он может вернуть список совпадений; тогда ни одно совпадение не является просто пустым списком (который также является "ложным"; это одна из тех ситуаций, в которых я просто использовал бы if x, чтобы увидеть, получил ли я что-нибудь обратно).

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

Ответ 2

Используйте if foo или if not foo. Для этого нет необходимости ни в ==, ни is.

Для проверки на None рекомендуется is None и is not None. Это позволяет вам отличить его от Ложного (или вещей, которые оценивают как Ложное, например "" и []).

Должен ли get_attr возвращаться None, будет зависеть от контекста. У вас может быть атрибут со значением None, и вы не сможете этого сделать. Я бы интерпретировал None как означающий "unset", а KeyError означал бы, что ключ не существует в файле.

Ответ 3

Если проверять правду:

if foo

Для ложных:

if not foo

Ни для кого:

if foo is None

Для не-нет:

if foo is not None

Для getattr() правильное поведение - не возвращать None, а вместо этого выдавать ошибку AttributError - если ваш класс не похож на defaultdict.

Ответ 4

Относительно того, вызывать ли исключение или вернуть None: это зависит от варианта использования. Любой из них может быть Pythonic.

Посмотрите на класс Python dict, например. x[y] подключается к dict.__getitem__ и вызывает KeyError, если ключа нет. Но метод dict.get возвращает второй аргумент (по умолчанию None), если ключ отсутствует. Они оба полезны.

Самая важная вещь, которую следует учитывать, - документировать это поведение в строке документации и убедиться, что ваш метод get_attr() делает то, что говорит.

Чтобы ответить на ваши другие вопросы, используйте следующие условные обозначения:

if foo:
    # For testing truthiness

if not foo:
    # For testing falsiness

if foo is None:
    # Testing .. Noneliness ?

if foo is not None:
    # Check explicitly avoids common bugs caused by empty sequences being false

Функции, которые возвращают True или False, вероятно, должны иметь имя, которое делает это очевидным для улучшения читабельности кода:

def is_running_on_windows():
    return os.name == 'nt'

В Python 3 вы можете "напечатать подсказку", что:

>>> def is_running_on_windows() -> bool:
...     return os.name == 'nt'
... 
>>> is_running_on_windows.__annotations__
{'return': bool}

Ответ 5

Вы можете непосредственно проверить, содержит ли переменная значение или нет, например, if var или not var.

Ответ 6

В примерах в документе PEP 8 (Руководство по стилю для кода Python) я видел, что вместо foo == None или foo != None используются foo is None или foo is not None.

Также в этом документе рекомендуется использовать if boolean_value вместо if boolean_value == True или if boolean_value is True. Так что я думаю, если это официальный путь Python. Мы, ребята из Python, тоже должны идти по этому пути.

Ответ 7

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

Ответ 8

Правда, а не None:

if foo:

Нет, нет:

if not foo:

Ответ 9

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

На следующих рисунках "нет".

x = False
if x:
    print 'yes'
else:
    print 'no'

Теперь давайте изменим x.

x = 'False'

Теперь в операторе печатается "да", потому что строка верна.

if x:
    print 'yes'
else:
    print 'no'

Однако это утверждение правильно выводит "нет".

if x == True:
    print 'yes'
else:
    print 'no'