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

Нулевая модель в Python недостаточно используется?

Время от времени я сталкиваюсь с таким кодом:

foo = Foo()
...
if foo.bar is not None and foo.bar.baz == 42:
   shiny_happy(...)

Что мне кажется, ну, бесстрашным, для меня.

В Objective-C вы можете отправлять сообщения в nil и получать nil в качестве ответа. Я всегда считал, что это очень удобно. Конечно, возможно реализовать шаблон Null в Python, однако, судя по результатам, данным Google, мне кажется, что это не очень широко используется. Почему?

Или даже лучше - это будет плохая идея, чтобы позволить None.whatever вернуть None вместо создания исключения?

4b9b3361

Ответ 1

PEP 336 - Сделать None Callable предложила аналогичную функцию:

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

Причина того, почему это было отвергнуто, была просто: "Это считается особенностью, при которой None вызывает ошибку при вызове".

Ответ 2

Извините, но этот код является pythonic. Я думаю, что большинство согласятся с тем, что "явный лучше, чем неявный" в Python. Python - это язык, который легко читается по сравнению с большинством, и люди не должны побеждать этого, написав критический код. Сделайте смысл очень понятным.

foo = Foo()
...
if foo.bar is not None and foo.bar.baz == 42:
    shiny_happy(...)

В этом примере кода ясно, что foo.bar иногда отсутствует на этом пути кода и что мы запускаем только shiny_happy(), если это не None, и .baz == 42. Очень ясно всем, что есть здесь и почему. То же самое нельзя сказать о нулевом шаблоне или try... кроме кода в одном из ответов, размещенных здесь. Одно дело, если ваш язык, например Objective-C или javascript, применяет нулевой шаблон, но на языке, где он вообще не используется, он просто создает путаницу и код, который трудно читать. При программировании на python выполняйте, как это делают pythonistas.

Ответ 3

Разве вы не могли попробовать? Pythonic way говорит Легче просить прощения, чем разрешения.

Итак:

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(...)

Ответ 4

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

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


Конечно, NULL, подобный SQL, был бы плохим для тестирования, что на самом деле банки на предложениях были либо истинными, либо ложными. Рассмотрим этот код: unittest.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 ошибочно возвращает null. Если я использую "нулевой шаблон" или язык имеет встроенный интерфейс, а conn имеет значение null, то conn.isOpen() имеет значение null, а not conn.isOpen() также является нулевым, поэтому утверждение проходит, хотя соединение явно не открыто.

Я склонен думать, что NULL - одна из худших особенностей SQL. И тот факт, что NULL молча проходит объект любого типа на других языках, не намного лучше. (Тони Хоар назвал нулевые ссылки "моя ошибка в миллиард долларов" .) Нам нужно меньше таких вещей, а не больше.

Ответ 5

Как говорили другие, PEP 336 описывает, почему это поведение.

Добавление чего-то вроде Groovy " безопасный оператор навигации" (?.) может в некоторых случаях сделать вещи более элегантными:

foo = Foo()

if foo.bar?.baz == 42:
    ...

Ответ 6

Я не думаю, что это хорошая идея. Вот почему. предположим, что у вас есть

foo.getValue()

Предположим, что getValue() возвращает число или None, если не найден. Теперь предположим, что foo не был случайно или ошибкой. В результате он вернет None и продолжит, даже если есть ошибка.

Другими словами, вы больше не можете отличить, нет ли значения (возвращает None) или если для начала была ошибка (foo была None). Вы изменяете договор возврата подпрограммы с фактом, который не находится под контролем самой процедуры, в конце концов переписывая ее семантику.

Ответ 7

Вот почему я не думаю, что хорошая идея:

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 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?

Как бы вы справились с этим случаем?

Ответ 8

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

getattr(foo.bar, 'baz', default_value)

Ответ 9

Лично я считаю, что исключение - это то, что должно произойти. Допустим, по какой-то причине вы пишете программное обеспечение для ракет в Python. Представьте себе атомную бомбу и скажем, что существует метод под названием "взрыв" (timeToExplode), а timeToExplode - как None. Я думаю, что вы были бы недовольны в конце дня, когда вы проиграли войну, потому что вы не нашли это при тестировании.

Ответ 10

foo = Foo()
...
if foo.bar is not None and foo.bar.baz == 42:
   shiny_happy(...)

Вышеописанное можно очистить, используя тот факт, что None разрешает False:

if foo.bar and foo.bar.baz == 42:
    shiny_happy(...)
else:
    not_happy(...)

Ответ 11

Я не программист на языке python (только начинаю изучать язык), но это похоже на бесконечную дискуссию о том, когда возвращать ошибку или бросать исключение, и факт в том, что (конечно, это только мнение) зависит.

Исключения должны использоваться для исключительных условий и, таким образом, перемещать проверки для редких ситуаций вне основного блока кода. Они не должны использоваться, если не найденное значение является обычным условием (подумайте о том, чтобы запросить левый дочерний элемент дерева, во многих случаях - все оставлено - оно будет null). Еще есть третья ситуация с функциями, возвращающими наборы значений, где вы можете просто хотеть вернуть допустимый пустой набор, чтобы облегчить код.

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

Когда null является допустимым возвращаемым значением, обработка выполняется в основном потоке управления, но это хорошо, поскольку это обычная ситуация и как таковая часть основного алгоритма (не следует за нулевым краем в графе).

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

И опять же, это всего лишь мнение, но, будучи моим, я стараюсь следовать ему:)