为什么异常/错误在Python中评估为True?
在很多地方,我需要从一个字典中获取某个值,但我需要先检查一下这个值的键是否存在。如果不存在,我就用一个默认值:
if self.data and self.data.has_key('key'):
value = self.data['key']
else:
value = self.default
....
我喜欢Python的一点是,它的与或运算符可以返回它们的一个操作数。我不太确定,但如果异常被视为假值,上面的代码可以这样重写:
value = self.data['key'] or self.default
我觉得错误应该被视为假值是很直观的(就像在bash编程中一样)。那么,Python为什么把异常当作真值来处理呢?
编辑:
我只是想表达我对“异常处理”这个话题的看法。根据维基百科的说法:
" 从一个例程的作者的角度来看,抛出异常是一种有用的方式来表示一个例程无法正常执行。例如,当输入参数无效(比如在除法中分母为零)或者它依赖的资源不可用(比如缺少文件或硬盘错误)时。在没有异常的系统中,例程需要返回一些特殊的错误代码。然而,这有时会因为半谓词问题而变得复杂,使用例程的人需要写额外的代码来区分正常返回值和错误值。 "
正如我所说,抛出异常只是一种返回函数的高级方式。“异常对象”只是指向一个数据结构的指针,这个数据结构包含了关于错误的信息,而如何在高级语言中处理这些错误则完全依赖于虚拟机的实现。我决定通过查看“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块时,它可以生成类似于上面的指令。通过这种实现,某人可以测试一个块是否有异常,或者将返回异常的函数视为“假”值。
5 个回答
你可以使用 get
方法:
value = self.data.get('key', self.default)
更新:你对 or
这个关键词的理解有误。self.default
这个值只有在 self.data['key']
的结果为假(False)时才会被使用,而不是当 'key' 在 self.data 中不存在的时候。如果 self.data 里没有 'key',还是会抛出一个异常。
在这个表达式中:
self.data['key'] or self.default
Python 解释器会先计算 self.data['key']
,然后检查它是否为真(True)。如果是真,那么这个结果就是整个表达式的结果。如果是假的话,self.default
就是这个表达式的结果。不过,如果在计算 self.data['key']
时,'key' 不在 self.data 里,就会抛出一个异常,这样整个表达式的计算就会中止,直到找到一个匹配的 except
块为止。并且如果抛出了异常,赋值操作也不会执行,value
仍然保持它最初的值。
你对异常的使用有些误解。异常是指出现了问题。它不仅仅是一个返回值,也不应该被当作返回值来处理。
明确的比隐含的要好。
因为异常是在出现问题时被触发的,所以你必须明确地写代码来捕获它们。这是故意的——你不应该把异常当作简单的False
来忽略,因为它们的意义远不止于此。
如果你像你所说的那样吞掉异常,你就无法知道代码哪里出错了。如果你引用了一个未定义的变量,会发生什么呢?这应该会给你一个NameError
,但如果它给你的是... False
?!
考虑一下你的代码块:
value = self.data['key'] or self.default
你希望self.data['key']
在key
不在self.data
中时返回False
。你觉得这种做法有什么问题吗?如果self.data == {'key': False}
呢?你就无法区分字典中存储的是False
,还是因为缺少键而返回的False
。
而且,如果这种情况更普遍地发生,所有的异常都被吞掉了,那你怎么区分KeyError
('key'不在self.data中
)和NameError
(self.data未定义
)呢?两者都会返回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
为什么在Python中,异常/错误会被评估为True呢?
在Python中,Exception
的实例会被评估为True
(编辑 请看下面@THC4k的评论,里面有很相关的信息)。这并不妨碍它们被抛出。
我不太确定,但如果异常被评估为False的话
在你的情况下,异常被评估为False
是不够的。它们还应该不被抛出,也不会在调用栈中传播。相反,它们必须在当下被“停止”,然后评估为False
。
我把这个问题留给更有经验的Python程序员来评论,为什么异常不会(或者不应该)被“停止”,而是评估为True
或False
。我猜这是因为它们被期望被抛出并传播。如果它们被停止并检查,那么它们就不再是“异常”了 =P。
但需要检查那个值的键是否存在,如果不存在,我就使用一些默认值
我能想到两种方法来在字典中获取默认值,前提是没有这个键。一个是使用defaultdict
,另一个是使用get
方法。
>>> from collections import defaultdict
>>> d = defaultdict(lambda: "default")
>>> d['key']
'default'
>>> d = dict()
>>> d.get('key', 'default')
'default'
>>>
顺便说一下,if key in dict
比dict.has_key(key)
更受欢迎。实际上,has_key()
在Python 3.0中已经被移除了。