Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do exceptions/errors evaluate to True in python?

In several places I have to retrieve some value from a dict, but need to check if the key for that value exists, and if it doesn't I use some default value :

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

One thing I like about python, is that and/or boolean operators return one of their operands. I'm not sure, but if exceptions evaluated to false, the above code could be rewriten as follows:

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

I think its intuitive that errors should evaluate to false(like in bash programming). Is there any special reason for python treating exceptions as true?

EDIT:

I just want to show my point of view on the 'exception handling' subject. From wikipedia:

" From the point of view of the author of a routine, raising an exception is a useful way to signal that a routine could not execute normally. For example, when an input argument is invalid (e.g. a zero denominator in division) or when a resource it relies on is unavailable (like a missing file, or a hard disk error). In systems without exceptions, routines would need to return some special error code. However, this is sometimes complicated by the semipredicate problem, in which users of the routine need to write extra code to distinguish normal return values from erroneous ones. "

As I said, raising exceptions are just a fancy way of returning from a function. 'Exception objects' are just a pointer to a data structure that contains information about the error and how that is handled on high level languages depend only on the implementation of the VM. I decided to look at how python 2.6.6 deals with exceptions by looking at output from the 'dis' module:

>>> 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        

Its clear that python has the exception concept at bytecode level, but even if it didnt, it could still make exception handling constructs behave like they do. Take the following function:

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

Both functions create the exception object in the same way as show in 0, 3 and 6. The difference is that the first call the RAISE_VARARGS instruction on the object(and stills returns None) and the second will just return the exception object to calling code. Now take the following bytecode(I'm not sure if this is correct) :

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

The above translates to something equivalent to this:

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

However, the compiler could generate something like the above instructions when it finds a try block. With this implementation someone could either test a block for an exception or treat functions that return an exception as a 'False' value.

like image 403
Thiago Padilha Avatar asked Nov 26 '25 15:11

Thiago Padilha


2 Answers

You are misunderstanding the use of exceptions. An exception is something gone wrong. It's not just a return value, and it shouldn't be treated as such.

Explicit is better than implicit.

Because an exception is raised when something goes wrong, you must explicitly write code to catch them. That's deliberate -- you shouldn't be able to ignore exceptions as merely False, because they're more than that.

If you swallowed exceptions as you seem to suggest, you wouldn't be able to tell when code went wrong. What would happen if you referenced an unbound variable? That should give you a NameError, but it would give you... False?!


Consider your code block:

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

You want self.data['key'] to return False if key is not a key of self.data. Do you see any problems with this approach? What if self.data == {'key': False}? You couldn't distinguish between the case of False being stored in the dictionary and False being returned because of a missing key.

Further, if this were changed more generally, so that all exceptions were swallowed (!), how would you distinguish between a KeyError ('key' not in self.data) and say a NameError (self.data not defined)? Both would evaluate to False.

Explicitly writing code to catch the exception solves this problem, because you can catch exactly those exception that you want:

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

There is a data structure already written that does this for you, though, because default dictionaries are very commonly needed. It's in collections:

>>> import collections
>>> data = collections.defaultdict(lambda: False)
>>> data['foo'] = True
>>> data['foo']
True
>>> data['bar']
False
like image 146
Katriel Avatar answered Nov 29 '25 05:11

Katriel


Why do exceptions/errors evaluate to True in python?

Instances of Exception evaluate to True (EDIT See @THC4k's comment below. Quite relevant information). That doesn't prevent them from being thrown.

I'm not sure, but if exceptions evaluated to false

In your context it wouldn't suffice that Exceptions evaluate to False. They should also not get thrown and be propagated down the call stack. Rather, they will have to be "stopped" on the spot and then evaluate to False.

I'll leave it to more experienced Pythonistas to comment on why Exceptions do not (or should not) get "stopped" and evaluate to True or False. I'd guess that this is because they are expected to be thrown and propagated. In fact they would cease to be "exceptions" if they were to be stopped and interrogated =P.

but need to check if the key for that value exists, and if it doesn't I use some default value

I can think of two options two ways to get a default value in the absence of a key in a dictionary. One is to use defaultdict. The other is to use the get method.

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

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

PS: if key in dict is preferred to dict.has_key(key). In fact has_key() has been removed in Python 3.0.

like image 39
Manoj Govindan Avatar answered Nov 29 '25 06:11

Manoj Govindan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!