依靠异常是不好的形式?

时间:2015-06-28 03:38:37

标签: python exception

我目前的情况是使用名为pygame的库在python中制作游戏。我有一个名为getTile()的方法,每次玩家移动时都返回一个tile。 lvlDict弥补了这个世界。

def getTile(self, pos):
    x = pos[0]
    y = pos[1]
    return self.lvlDict[x,y].kind

我正考虑将其改为此

def getTile(self, pos):
    try:
        x = pos[0]
        y = pos[1]
        return self.lvlDict[x,y].kind
    except KeyError:
        return None

如果没有,那么会有什么解释。可能只是回到以前的位置。这本身就是坏的还是可以接受的? 即使这只是一个意见问题,我也想知道人们对它的看法。

5 个答案:

答案 0 :(得分:11)

在这种情况下,我认为请求宽恕比在检查和索引之间在字典中发生某些事情的请求允许更好,但总的来说,我倾向于不使用逻辑异常,完全是因为异常很慢。

In [1]: d = {i:i for i in xrange(10000)}

In [4]: def f(d):
    try:                     
        d["blue"]
    except KeyError:
        pass   

In [5]: def g(d):
    if "blue" in d: d["blue"]  

#Case: Key not in Dict

In [7]: timeit f(d)
1000000 loops, best of 3: 950 ns per loop

In [8]: timeit g(d)
10000000 loops, best of 3: 135 ns per loop

#Case: Key in Dict

In [9]: d["blue"] = 8 

In [10]: timeit f(d)
10000000 loops, best of 3: 151 ns per loop

In [11]: timeit f(d)
10000000 loops, best of 3: 151 ns per loop

答案 1 :(得分:9)

例外情况很慢,所以最好在特殊情况下保存它们。如果这是一个通常不会出现的意外情况,那么例外是明智的。如果空格子很常见,最好使用if检查。

另一种看待它的方法是用户是否可以触发它。如果玩家仅仅通过在地图上移动就可以引起这种情况,那么它可能不应该是一个例外。例外情况更适合当你遇到不是用户错误的意外情况时,例如" oops,我忘了在这里放一堵墙,玩家在地图上徘徊"或者"我希望这个文件存在,并且它不是。"

答案 2 :(得分:7)

虽然异常通常比检查可能发生的可能故障要慢,但在实际应用中通常也不会产生差异。特别是,对于使用Python作为选择语言的应用程序,它通常没有显着差异。因此,正如EAFP的Python词汇表所解释的那样:

  

比获得许可更容易请求宽恕。这个常见的Python   编码风格假定存在有效的键或属性   如果假设被证明是假的,则捕获异常。这干净又快   风格的特点是存在许多尝试和除外   声明。该技术与许多人共同的LBYL风格形成鲜明对比   其他语言,如C.

实际上,Python中常见的是使用异常(并请求宽恕)来处理错误,即使这些错误预计会经常发生并定期发生。

现在,在您的特定情况下,简单地检查密钥是否在字典中(如其他答案所示)似乎同样优雅。不过,你不应该害怕使用异常,并且它们的使用(即使是预期的)错误处理完全是Pythonic。

答案 3 :(得分:3)

在python中使用异常来实现代码的正常流程并不是坏事。包括生成器在内的一些语言结构使用了这种方法,它通过引发StopIteration来发出不再信号的信号。

其他答案指出,依赖异常可能会使代码变慢,而不依赖它可能会引入竞争条件。您可以同时使用if构造和except块来避免这两个问题。但这引入了另一个问题,因为它使您的代码更复杂,因此更容易出错。

我认为正确性通常比性能更重要(如果这段代码真的对性能至关重要,那么你可能不会在python中开始编写它)。因此,如果dict在检查密钥的存在与访问密钥之间是否有可能发生变化,我肯定会接受例外。如果你知道这种比赛不可能发生,那么我建议你采用你认为最具可读性的方法。

您的代码还有另一个方面,它确实看起来很糟糕。在try区块内,我看到了可以引发KeyError的三个不同位置。我猜你只是想抓住其中一个地方的例外。通过捕获可能引发的其他两个位置的异常,您可以更难调试代码。触发错误的条件将从描述性跟踪崩溃变为巧妙的错误行为。

因此只将这些行放在try块中,这真的需要在那里。

我猜这种结构更接近你的意图:

def getTile(self, pos):
    x = pos[0]
    y = pos[1]
    try:
        return self.lvlDict[x,y].kind
    except KeyError:
        return None

如果你有一个try块,其中第一行可以引发异常,而其余的行都在try块内,因为你不希望它们在异常的情况下被执行,那么你应该是使用try - except - else构造。

答案 4 :(得分:1)

从非Python的角度来看,我会说异常应仅用于处理无效案例,并提出问题:我们是在讨论名义案例,边缘案例还是无效输入/结果案例?如果你的"无" case是有效的,但边缘情况,我会说它应该由程序逻辑处理(不要抛出异常)。如果"无"可能发生,但真的永远不应该,然后它应该由异常处理。

不熟悉Python,但假设与其他编程环境相似,例如C#:

  • 使用try / catches包装代码块对性能影响可忽略不计;一旦抛出异常,就会在幕后发生,这会产生非常重要的性能成本。

  • 抛出异常的典型案例是,当您无法从逻辑上继续前进时,必须放弃当前的程序状态并找到回到某些早期已知状态的方法,您可以从中恢复。这就是为什么例外机制设计的,即使它们有更广泛的用途。

  • 恰当地使用,例外可以帮助您编写更清晰的代码,而不会使用额外的if-then逻辑混乱,只留下真正的" business"逻辑。它们还可以让您远离笨拙的函数签名,并使用粗体返回值来表示信号错误情况。然而,过度使用,异常最终会隐藏" business"逻辑,混淆你的代码。