Нулевой шаблон в Python недостаточно используется? - PullRequest
18 голосов
/ 06 января 2010

Время от времени я сталкиваюсь с кодом, подобным этому:

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

Что, на мой взгляд, не пифонично.

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

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

Ответы [ 11 ]

19 голосов
/ 06 января 2010

PEP 336 - «Запретить вызов» предложил аналогичную функцию:

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

Причина, по которой он был отклонен, заключалась в следующем: «Считается, что функция None вызывает ошибку при вызове».

14 голосов
/ 06 января 2010

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

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

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

13 голосов
/ 06 января 2010

Не могли бы вы попробовать, кроме? Пифонический путь говорит: Прошить прощения легче, чем разрешение .

Итак:

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(...)
11 голосов
/ 06 января 2010

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

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


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

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

4 голосов
/ 06 января 2010

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

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

foo = Foo()

if foo.bar?.baz == 42:
    ...
3 голосов
/ 06 января 2010

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

foo.getValue()

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

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

2 голосов
/ 06 января 2010

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

getattr(foo.bar, 'baz', default_value)
2 голосов
/ 06 января 2010

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

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?

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

1 голос
/ 06 января 2010

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

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

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

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

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

Опять же, это просто мнение, но, будучи моим, я склонен следовать ему:)

1 голос
/ 06 января 2010
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(...)
...