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.