views:

131

answers:

5

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.

+2  A: 

You can use get:

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

Update: You are misinterpreting the or keyword there. The self.default value is only used if self.data['key'] evaluates to False, not if 'key' does not exist in self.data. If self.data contains no 'key', an exception is still raised.

In the expression:

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

the Python interpreter will evaluate self.data['key'] first, then check whether it evaluates to True. If it is True, then it is the result of the entire expression. If it is false, self.default is the result of the expression. However, in evaluating self.data['key'], 'key' is not in self.data, then an exception is raised, and the evaluation of the entire expression is aborted, and indeed the containing block until a matching except block is found somewhere along the stack. The assignment is also not executed in case an exception is raised, and value remains at whatever initial value it had.

Ivo
Vote for the info about the 'get' method, but I'm not misinterpreting the or operator, I just said that if exceptions evaluated to false(self.data['key'] would evaluate to false in case it didn't had the key) then 'self.value' would be used.
Thiado de Arruda
I think your are misinterpreting the word "exception" here. If 'key' is not present in the dictionary, an Exception would be raised and the rest of the term would not be evaluated, so the 'or' operator becomes meaningless in this case
knitti
Maybe, but isn't raising an exception just a fancy way of returning something from a block of code? I don't know how the python interpreter works, but couldn't it simply return any exception found at the top of the stack to the calling code?
Thiado de Arruda
No. Raising an exception is breaking out of the current cotrol flow, because something went wrong and one can't handle this sanely at his location. Part of the deal is, that you don't exactly know (at the time of the raise) where you land, because this depends on where in the call chain you catch the exception
knitti
Exception handling breaks the code flow, as @knitti pointed out. That makes it useful for quickly returning from a code path that makes no sense to continue executing otherwise. I have updated my answer with a bit more detail.
Ivo
I agree. As I said before, I'm not stating that raising an exception in python returns a value, I'm just saying that if it did and the returned value would be equivalent to false, it would make possible writing snippets like the one I showed while still mantaining the same functionality. In my opinion, raising an exception can be viewed as returning a value(the exception) to the calling code. Lets say that whenever an Exception object was loaded in the stack, the python interpreter automatically returned that to the calling code...
Thiado de Arruda
... then you could still have try blocks. The try blocks would be a way of telling the interpreter :"hey, if you find an exception in the stack, jump to the except: block for the corresponding exception. Returning from a function is breaking the flow to jump back to calling code.
Thiado de Arruda
Yes. But how does this differ from your original question? I'm losing track a bit. :) The point is, if you have an expression anywhere in your code that causes an exception to be thrown, that expression does not have a return value because it doesn't return. Anything that depends on the return value of the expression cannot complete (such as the assignment to value in your example). Exceptions are just that: unexpected situations that need to be handled differently.
Ivo
Check my question edit.
Thiado de Arruda
+5  A: 

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.

Manoj Govindan
You say "*Instances* of Exception evaluate to `True`" but `Exception` itself evaluates to `True` too (and so do classes and functions). Actually *everything* evaluates to `True` by default. Only a few builtins overwrite this, such as empty containers (`(),[],{}`) and the zeroes (`0, 0.0, 0j, False`).
THC4k
I forgot `""`, kind of a empty container too.
THC4k
@THC4k: You are right of course. I didn't mean to exclude anything else. I have updated my answer to point to your comment. Thanks.
Manoj Govindan
+6  A: 

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
katrielalex
+1 for `Explicit is better than implicit.`
knitti
A: 

To answer what you are trying to do, rather than your problem with exceptions, if self.datawas a defaultdictwith a default value of Nonethen if the key wasn't found it would return Nonewhich would evaluate as Falseand then you would get self.defaultas desired.

Note that this would cause problems if the value found in self.datawas 0, False, None or any other value that evaluates as False. If these values might be in there then I think you will have to go with katrielalex's try/except answer.

neil
+2  A: 

You had

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

This is the same:

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

Now we write it nicer:

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

It's easy to transform this into a inline if:

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

In this case it is simply:

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

But not if you want to do something like this:

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

This is different from this

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

because self.get_default('key') will be called unconditionally (before the call to get)!

THC4k
IMHO, technically the inline 'if' form is more explicit the using the `dict.get()` method.
martineau
Surprisingly, the inline 'if' form was nearly 1.6x faster in some timeit tests I just ran with the key in the dictionary half of the time.
martineau
FWIW, in my timeit tests I used `self.default` not `self.get_default('key')` for when the key wasn't in the dictionary.
martineau
You are wrong in your first example - your change is not equivalent to the original. What if there is no "data" member at all? In the original it will return default, in your case it will throw exception...
Nas Banov
@Nas Banov: No, both will throw a exception in this case and there is no difference.
THC4k
@THC4k: oh yeah, what if `self.data=None` - i reckon the two snippets are not equivalent. i "misspoke" but seems the intent of the code was to have `if hasattr(self,"data") and ...`
Nas Banov