为什么在python中异常/错误评估为True?

时间:2010-09-22 10:46:23

标签: python exception-handling

在一些地方我必须从dict中检索一些值,但是需要检查该值的键是否存在,如果不存在,我会使用一些默认值:

    if self.data and self.data.has_key('key'):
        value = self.data['key']
    else:
        value = self.default
    ....

我喜欢python的一件事是,和/或布尔运算符返回其中一个操作数。我不确定,但如果异常评估为false,则上述代码可以重写如下:

    value = self.data['key'] or self.default

我认为直观的错误应该评估为false(比如在bash编程中)。 python将异常处理为true是否有任何特殊原因?

编辑:

我只想表明我对'异常处理'主题的看法。来自wikipedia

” 从例程的作者的角度来看,引发异常是表示例程无法正常执行的有用方法。例如,当输入参数无效时(例如,除法中的零分母)或当它依赖的资源不可用时(如丢失的文件或硬盘错误)。在没有例外的系统中,例程需要返回一些特殊的错误代码。然而,这有时因半无差别问题而变得复杂,其中例程的用户需要编写额外的代码来区分正常的返回值和错误的返回值。 “

正如我所说,提出异常只是从函数返回的一种奇特方式。 “异常对象”只是指向数据结构的指针,该数据结构包含有关错误的信息以及如何在高级语言上处理错误仅取决于VM的实现。我决定通过查看'dis'模块的输出来了解python 2.6.6如何处理异常:

>>> import dis
>>> def raise_exception():
...     raise Exception('some error')
... 
>>> dis.dis(raise_exception)
  2           0 LOAD_GLOBAL              0 (Exception)
              3 LOAD_CONST               1 ('some error')
              6 CALL_FUNCTION            1
              9 RAISE_VARARGS            1
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE        

很明显python在字节码级别有异常概念,但即使它没有,它仍然可以使异常处理结构表现得像它们那样。采取以下功能:

def raise_exception():
...     return Exception('some error')
... 
>>> dis.dis(raise_exception)
  2           0 LOAD_GLOBAL              0 (Exception)
              3 LOAD_CONST               1 ('some error')
              6 CALL_FUNCTION            1
              9 RETURN_VALUE

两个函数都以与0,3和6中显示相同的方式创建异常对象。不同之处在于,第一个调用对象上的RAISE_VARARGS指令(并且仍然返回None),第二个函数只返回异常对象调用代码。现在采用以下字节码(我不确定这是否正确):

0  LOAD_GLOBAL      (isinstance) #Loads the function 'isinstance'
3  LOAD_GLOBAL      (raise_exception) #loads the function 'raise_exception'
6  CALL_FUNCTION    #call the function raise_exception(which will push an Exception instance to the stack
9  LOAD_GLOBAL      (Exception) #load the Exception class
12 CALL_FUNCTION    #call the 'isinstance function'(which will push 'True to the stack)
15 JUMP_IF_TRUE     (to 27) #Will jump to instruction 33 if the object at the top of stack evaluates to true
18 LOAD_CONS        ('No exceptions were raised')
21 PRINT_ITEM
24 PRINT_NEWLINE    
27 LOAD_CONS        ('Exception caught!')
21 PRINT_ITEM
24 PRINT_NEWLINE

以上翻译成与此相当的东西:

if isinstance(raise_exception(), Exception):
    print 'Exception caught!'
else:
    print 'No exceptions were raised'

但是,编译器在找到try块时可能会生成类似上述指令的内容。有了这个实现,有人可以测试一个异常块,或者将异常作为'False'值处理的函数。

5 个答案:

答案 0 :(得分:9)

您误解了异常的使用。一个例外是出了问题。它不仅仅是一个返回值,也不应该这样对待。

  

明确比隐含更好。

因为出现错误时会引发异常,所以必须显式编写代码来捕获它们。这是故意的 - 你不应该忽视仅仅False的例外,因为它们不仅仅是那个。

如果你似乎建议吞下异常,你将无法判断代码何时出错。如果您引用了未绑定的变量会发生什么?这应该会给你一个NameError,但它会给你...... False?!


考虑你的代码块:

value = self.data['key'] or self.default

如果self.data['key']不是False的密钥,您希望key返回self.data。你觉得这种方法有什么问题吗?如果self.data == {'key': False}怎么办?您无法区分False存储在字典中的情况和由于缺少密钥而返回的False

此外,如果更频繁地更改,以便吞下所有例外(!),您将如何区分KeyError'key' not in self.data)并说出NameErrorself.data not defined)?两者都会评估为False

显式编写代码以捕获异常可以解决此问题,因为您可以准确捕获所需的异常:

try:
    value = self.data['key']
except KeyError:
    value = self.default

但是,已经有一个数据结构可以为您执行此操作,因为通常需要使用默认字典。它位于collections

>>> import collections
>>> data = collections.defaultdict(lambda: False)
>>> data['foo'] = True
>>> data['foo']
True
>>> data['bar']
False

答案 1 :(得分:5)

  

为什么在python中异常/错误评估为True?

Exception的实例评估为True编辑请参阅下面的@THC4k评论。相关信息)。这并不妨碍他们抛出

  

我不确定,但是如果异常评估为假

在您的上下文中,例外评估为False是不够的。它们也不应该被抛出并沿着调用堆栈传播。相反,他们必须在现场“停止”,然后评估为False

我会留给更有经验的Pythonistas评论为什么例外不会(或不应该)“停止”并评估为TrueFalse。我猜这是因为它们会被抛出并传播。事实上,如果他们被停止和审讯= P,他们将不再是“例外”。

  

但是需要检查该值的键是否存在,如果不存在,我会使用一些默认值

我可以想到两种方法可以在字典中没有键的情况下获得默认值。一种是使用defaultdict。另一种是使用get方法。

>>> from collections import defaultdict
>>> d = defaultdict(lambda: "default")
>>> d['key']
'default'

>>> d = dict()
>>> d.get('key', 'default')
'default'
>>> 

PS:if key in dict优先于dict.has_key(key)。事实上,{3.0}中的has_key()已经removed

答案 2 :(得分:3)

您可以使用get

value = self.data.get('key', self.default)

更新:您在那里误解了or关键字。 self.default值仅在self.data['key']求值为False时使用,而不是在self.data中不存在'key'时使用。如果self.data不包含'key',则仍会引发异常。

在表达式中:

self.data['key'] or self.default

Python解释器将首先评估self.data['key'],然后检查它是否计算为True。如果它是True,则它是整个表达式的结果。如果为false,则self.default是表达式的结果。但是,在评估self.data ['key']时,'key'不在self.data中,然后引发异常,并且整个表达式的计算被中止,实际上包含块直到匹配{{1在堆栈的某处找到块。如果引发异常,也不会执行赋值,并且except保留在它具有的任何初始值。

答案 3 :(得分:2)

你有

if self.data and self.data.has_key('key'):
    value = self.data['key']
else:
    value = self.default

这是相同的:

if self.data.has_key('key'): # if it has the key, it's not empty anyways
    value = self.data['key']
else:
    value = self.default

现在我们写得更好了:

if 'key' in self.data: # `in` does just what you expect
    value = self.data['key']
else:
    value = self.default

如果符合以下条件,很容易将其转换为内联:

value = (self.data['key'] if 'key' in self.data else self.default)

在这种情况下,它只是:

value = self.data.get('key', self.default)

但如果你想做这样的事情,那就不行了:

value = (self.data['key'] if 'key' in self.data else self.get_default('key'))

这与此不同

value = self.data.get('key', self.get_default('key'))

因为self.get_default('key')将被无条件地称为 (在调用get之前)!

答案 4 :(得分:0)

要回答您要执行的操作,而不是您的例外问题,如果self.datadefaultdict且默认值为None,那么如果找不到密钥它将返回None,它将评估为False,然后根据需要获得self.default

请注意,如果self.data中的值为0FalseNone或任何其他评估为False的值,则会导致问题。如果这些值可能在那里,那么我认为你必须使用katrielalex的尝试/除了答案。