Python中的空模式未被充分利用?

时间:2010-01-06 15:44:21

标签: python error-handling null

我偶尔遇到这样的代码:

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

对我来说,这似乎是单声道的。

在Objective-C中,您可以将消息发送到nil并获得nil作为答案。我一直认为这很方便。当然可以实现Null pattern in Python,但是根据Google给出的结果判断,它似乎并没有被广泛使用。为什么?

甚至更好 - 让None.whatever返回None而不是引发异常是不是一个坏主意?

12 个答案:

答案 0 :(得分:19)

PEP 336 - Make None Callable提出了类似的功能:

  

None应该是使用任何参数调用时的可调用对象   没有副作用,并返回无。

它被拒绝的原因只是“它被认为是一种功能,在调用时会引发错误。”

答案 1 :(得分:14)

对不起,但那段代码是pythonic。我认为大多数人会同意Python中的“显式优于隐式”。与大多数人相比,Python是一种易于阅读的语言,人们不应该通过编写神秘的代码来打败它。使意思非常明确。

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

在这个代码示例中,很明显foo.bar在这个代码路径上有时是None,而且如果它不是None,我们只运行shiny_happy(),而.baz == 42.非常清楚任何人是什么继续在这里,为什么。对于null模式或try ...,除了此处发布的答案中的代码之外,不能说同样的情况。如果您的语言(如Objective-C或javascript强制执行空模式)是一回事,但在一种根本不使用它的语言中,它只会产生难以阅读的混淆和代码。在python中编程时,请像pythonist那样进行。

答案 2 :(得分:13)

你能不能尝试除外? Pythonic方式说It is Easier to Ask for Forgiveness than Permission

所以:

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

答案 3 :(得分:11)

这种便利是以尽可能不及时检测到的愚蠢错误为代价的,尽可能接近有缺陷的代码行。

我认为这个特殊功能的便利性最多只是偶尔出现,而愚蠢的错误一直发生


当然,类似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错误地返回null。如果我使用“空模式”,或者语言内置了它,并且conn为空,则conn.isOpen()为空,not conn.isOpen()也为空,因此断言传递,即使连接明显没有打开。

我倾向于认为NULL是SQL最糟糕的功能之一。 null以其他语言为任何类型的对象静默传递的事实并没有好多少。 (Tony Hoare称为空引用“my billion-dollar mistake”。)我们需要更少的东西,而不是更多。

答案 4 :(得分:4)

正如其他人所说,PEP 336描述了为什么会这样做。

在某些情况下,添加像Groovy的“safe navigation operator”(?.)这样的东西可能会让事情变得更优雅:

foo = Foo()

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

答案 5 :(得分:3)

我认为这不是一个好主意。这就是原因。假设你有

foo.getValue()

假设getValue()返回一个数字,或None如果找不到。 现在假设foo不是偶然或者是错误。因此,它会返回None,并继续,即使存在错误。

换句话说,您无法再区分是否没有值(返回None)或是否有错误(foo开始为None)。您正在使用不受例程本身控制的事实来更改例程的返回契约,最终覆盖其语义。

答案 6 :(得分:2)

这就是为什么我认为这不是一个好主意:

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?

你会如何处理这个案子?

答案 7 :(得分:2)

虽然我完全同意这里的其他答案,但是当None在被要求成员时提出异常是一件好事,这是我有时使用的一个小模式:

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

答案 8 :(得分:1)

我个人认为抛出异常是应该发生的事情。可以说,出于某种原因,你正在用Python编写导弹软件。想象一下原子弹,让我们说有一种名为explode(timeToExplode)的方法,并且timeToExplode作为None传入。我认为当你输掉战争的那一天结束时你会感到不快,因为你没有在测试中找到它。

答案 9 :(得分:1)

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

答案 10 :(得分:1)

我不是一个python程序员(刚刚开始学习这门语言),但这似乎是关于何时返回错误或抛出异常的永无止境的讨论,事实是(当然这只是一个意见)它取决于。

例外情况应该用于例外情况,因此可以检查主要代码块之外的罕见情况。当未找到的值是常见条件时,不应该使用它们(想想请求树的左子,在许多情况下 - 所有叶子 - 它将为null)。还有第三种情况,其函数返回值集,您可能只想返回一个有效的空集以简化代码。

我相信遵循上述建议,代码更清晰。当情况很少时,您不需要担心null(将调用异常),因此较少使用的代码将被移出主块。

当null是一个有效的返回值时,处理它是在主要的控制流中,但这是好的,因为它是常见的情况,并且是主算法的一部分(不遵循图中的空边)。

在第三种情况下,当从一个可能不返回值的函数请求值时,返回一个空集简化了代码:您可以假设返回的容器存在并处理所有找到的元素而不在代码中添加额外的检查

然后,这只是一个意见,但作为我的意见,我倾向于遵循它:)

答案 11 :(得分:0)

免责声明:这可能不是一个好主意,因为它不太易读,但为了完整性起见,我只是在此处添加它。

利用and的工作原理

如果您想将任意函数transform应用于可为空的变量var: Optional[T],则返回表达式,如果变量不是None,则返回该计算结果;或者其他None(按照Java Optional的精神):

var and transform(var)

这适用于任何函数,而不仅限于布尔上下文(例如if子句的条件)。原因是and运算符的定义如下(from the docs):

表达式x and y首先计算x;如果x为假,则返回其值;否则,将评估y并返回结果值。

因此,如果varNone,则var and transform(var)的计算结果为None(不评估transform(var),因为Python中的布尔运算符正在短路) ,如果不是,它将求出transform(var)的结果并返回。

虽然不如适当的null模式那么简洁,但仍然比

更为冗长
transform(var) if var is not None else None

示例

import datetime


def null_pattern_demo(x: datetime.datetime):
    return x and x.timestamp()

>>> null_pattern_demo(None)
>>> null_pattern_demo(datetime.datetime(2020, 6, 29))
1593381600.0


连锁

在Python 3.8+中,借助赋值表达式,您甚至可以链接此模式,即,您可以组成一个函数序列并在任何计算返回None的情况下返回None,或者否则返回最终结果:

var and (r := f(var)) and (r := g(r)) and (r := h(r))

任何中间步骤(或None本身)返回var时,它将评估为None。副作用是,最终结果也将存储在r中。

示例

def f(x, y):
    return 2*x if x == y else None


def g(x, y):
    return x + y if y != 42 else None


def h(x):
    return x**2

如果我们尝试一次性应用fgh,如果其中一个中间步骤返回了None,则会出错:

>>> h(g(f(2, 2), 42))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in h
TypeError: unsupported operand type(s) for ** or pow(): 'NoneType' and 'int'

但是,相反,我们可以使用上面的技巧:

>>> (x := None) and (x := f(x, 3)) and (x := g(x, 1)) and (x := h(x))
>>> (x := 2) and (x := f(x, 3)) and (x := g(x, 1)) and (x := h(x))
>>> (x := 2) and (x := f(x, 2)) and (x := g(x, 42)) and (x := h(x))
>>> (x := 2) and (x := f(x, 2)) and (x := g(x, 1)) and (x := h(x))
25