Время от времени я сталкиваюсь с кодом как это:
foo = Foo()
...
if foo.bar is not None and foo.bar.baz == 42:
shiny_happy(...)
Который кажется, ну, в общем, unpythonic мне.
В Objective C можно отправить сообщения в ноль и получить ноль как ответ. Я всегда думал, что это довольно удобно. Конечно, возможно реализовать Пустой шаблон в Python, однако судящий по Google результатов дал мне, кажется, что это очень широко не используется. Почему это?
Или еще лучше — это была бы плохая идея позволить None.whatever не возвратить Ни один вместо того, чтобы повысить исключение?
PEP 336 - Make None Callable предложил схожее свойство:
None should be a callable object that when call with any arguments не имеет побочных эффектов и возвращает Нет.
Причина, по которой она была отклонена, заключалась в том, что она просто "считается особенностью, при вызове которой Никто не допускает ошибки"
.Нельзя ли попробовать, кроме как? Питонический способ говорит Легче попросить прощения, чем разрешения .
Итак:
try:
if foo.bar.baz == 42:
shiny_happy(...)
except AttributeError:
pass #or whatever
Или сделать это без возможности заставить замолчать больше исключений, чем хотелось бы:
try:
baz = foo.bar.baz
except AttributeError:
pass # handle error as desired
else:
if baz == 42:
shiny_happy(...)
Как говорили другие, PEP 336 описывает, почему такое поведение.
Добавив что-то вроде "Groovy's " safe navigation operator" (?].
), возможно, сделает вещи более элегантными в некоторых случаях:
foo = Foo()
if foo.bar?.baz == 42:
...
Я не думаю, что это хорошая идея. Вот почему. Предположим, у вас есть
foo.getValue()
Предположим, что getValue()
возвращает число, или None
если не найдено.
Теперь предположим, что foo
не было случайностью или ошибкой. В результате, он вернет None
, и продолжит, даже если есть ошибка.
Другими словами, вы больше не сможете различить, нет ли значения (возвращает None
) или есть ли ошибка (foo
было None
, если не найдено). Вы изменяете договор о возврате рутины с фактом, который не контролируется самой рутиной, в конечном итоге переписывая ее семантику.
Вот почему я не думаю, что это хорошая идея:
foo = Foo() // now foo is None
// if foo is None, I want to raise an exception because that is an error condition.
// The normal case is that I expect a foo back whose bar property may or may not be filled in.
// If it's not filled in, I want to set it myself.
if not foo.bar // Evaluates to true because None.bar is None under your paradigm, I think
foo.bar = 42 // Now what?
Как бы вы справились с этим делом?
Хотя я полностью согласен с другими ответами здесь, которые говорят, что это хорошо, что Нет
поднимает вопрос об исключении, когда меня спрашивают о члене, это небольшая закономерность, которую я иногда использую:
getattr(foo.bar, 'baz', default_value)
Лично я считаю, что нужно сделать исключение. Скажем так, по какой-то причине вы пишете программы для ракет на Python. Представьте себе атомную бомбу и скажем, что был метод под названием explode(timeToExplode) и timeToExplode был передан как None. Я думаю, что в конце концов вы были бы несчастны, когда проиграли войну, потому что не нашли этого в тестировании
.Я не программист-питон (только начинаю изучать язык), но это похоже на бесконечную дискуссию о том, когда возвращать ошибку или бросать исключение, и дело в том, что (конечно, это только мнение) от этого зависит.
Исключения следует использовать для исключительных условий, и как таковые перемещать проверки на редкие ситуации за пределы основного блока кода. Их не следует использовать, когда неучтенное значение является обычным условием (подумайте о том, чтобы запросить левое дитя дерева, во многих случаях -- все листья -- оно будет нулевым). Опять же, есть третья ситуация с функциями, возвращающими наборы значений, в которой, чтобы облегчить код, можно просто вернуть действительное пустое множество.
Я считаю, что следуя вышеприведенному совету, код намного более читабелен. Когда ситуация редка, не стоит беспокоиться о нуле (будет вызвано исключение), поэтому менее используемый код переносится из основного блока.
Когда ноль - это действительное возвращаемое значение, его обработка находится в основном потоке управления, но это хорошо, так как это обычная ситуация и как таковая часть основного алгоритма (не следуйте за нулевым краем на графике).
В третьем случае, при запросе значений у функции, которая, возможно, не сможет вернуть ни одного значения, возврат пустого набора упрощает код: можно предположить, что возвращаемый контейнер существует и обрабатывать все найденные элементы без добавления дополнительных проверок в коде.
И опять же, это всего лишь мнение, но будучи моим, я склонен следовать ему :)
.foo = Foo()
...
if foo.bar is not None and foo.bar.baz == 42:
shiny_happy(...)
Вышеизложенное можно очистить, используя тот факт, что Ничто не разрешается Ложно:
if foo.bar and foo.bar.baz == 42:
shiny_happy(...)
else:
not_happy(...)
Извините, но этот код питонический. Думаю, большинство согласится с тем, что на Python "explicit is better than implicit". Python - это язык, который легко читается по сравнению с большинством, и люди не должны победить это, написав загадочный код. Сделайте смысл очень ясным.
foo = Foo()
...
if foo.bar is not None and foo.bar.baz == 42:
shiny_happy(...)
В этом примере кода ясно, что foo.bar иногда является None на этом пути кода, и что мы запускаем shiny_happy() только если это не None, а .baz == 42. Очень понятно, что здесь происходит и почему. То же самое нельзя сказать о нулевом шаблоне, или попытке ... кроме кода в одном из ответов, размещенных здесь. Одно дело, если ваш язык, как Objective-C или javascript, применяет нулевой шаблон, но на языке, где он вообще не используется, это просто создаст путаницу и код, который трудно прочитать. При программировании на питоне делайте то же, что и питонисты.
Удобство достигается за счет того, что глупые ошибки не обнаруживаются в кратчайшие сроки, максимально приближенные к баггированной строке кода.
Я думаю, что удобство этой особенности было бы в лучшем случае случайным, тогда как немые ошибки случаются все время .
Конечно, SQL-подобный NULL был бы плох для тестирования, который на самом деле банкирует на предложениях, которые являются либо правдивыми, либо ложными. Рассмотрим этот код из юниттеста.py:
class TestCase:
...
def failUnless(self, expr, msg=None):
"""Fail the test unless the expression is true."""
if not expr: raise self.failureException, msg
Теперь предположим, что у меня есть тест, который делает так:
conn = connect(addr)
self.failUnless(conn.isOpen())
Предположим, что connect
ошибочно возвращает ноль. Если я использую "нулевой паттерн", или язык имеет его встроенный, и connect
равен нулю, то connect.isOpen()
тоже равен нулю, и not conn.isOpen()
тоже равен нулю, поэтому утверждение passes, несмотря на то, что соединение явно не разорвано.
Я склонен считать NULL
одной из наихудших особенностей SQL. А то, что null
бесшумно передает для объекта любого типа на других языках, не намного лучше. (Тони Хоар назвал нулевые ссылки "моей миллиардной ошибкой".). Нам нужно меньше таких вещей, не больше.