views:

129

answers:

7

I have code which looks something like this:

thing_index = thing_list.index(thing)
otherfunction(thing_list, thing_index)

ok so that's simplified but you get the idea. Now thing might not actually be in the list, in which case I want to pass -1 as thing_index. In other languages this is what you'd expect index() to return if it couldn't find the element. In fact it throws a ValueError.

I could do this:

try:
    thing_index = thing_list.index(thing)
except ValueError:
    thing_index = -1
otherfunction(thing_list, thing_index)

But this feels dirty, plus I don't know if ValueError could be raised for some other reason. I came up with the following solution based on generator functions, but it seems a little complex:

thing_index = ( [(i for i in xrange(len(thing_list)) if thing_list[i]==thing)] or [-1] )[0]

Is there a cleaner way to achieve the same thing? Let's assume the list isn't sorted.

+7  A: 

There is nothing "dirty" about using try-except clause. This is the pythonic way. ValueError will be raised by the .index method only, because it's the only code you have there!

To answer the comment:
In Python, easier to ask forgiveness than to get permission philosophy is well established, and no index will not raise this type of error for any other issues. Not that I can think of any.

SilentGhost
Surely exceptions are for exceptional cases, and this is hardly that. I wouldn't have such a problem if the exception was more specific than ValueError.
Draemon
I know it can only be thrown from that *method* but is it guaranteed to only be thrown for that *reason*? Not that I can think of another reason index would fail..but then aren't exceptions for exactly those things you may not think of?
Draemon
@Draemon: That's why it checks only for `ValueError` and not just any form of exception.
MAK
Isn't `{}.get(index, '')` more pythonic? Not to mention shorter more readable.
voyager
@voyager: `.get` is not defined for lists. It's not clear what dictionary has to do with this question.
SilentGhost
I use dict[key] when I expect the key to exist and dict.get(key) when I'm not sure, and I *am* looking for something equivalent here. Returning `None` instead of -1 would be fine, but as you commented yourself, str.find() returns -1 so why shouldn't there be list.find() that does the same thing? I'm not buying the "pythonic" argument
Draemon
@Draemon: well `str.find` is a rudiment. There isn't a built-in equivalent, the code that you have is 100% equivalent to what `find` would look like if it'd been written in Python.
SilentGhost
But the point is that the most pythonic solution is to use *only* try/except and not the -1 sentinel value at all. I.E. you should rewrite `otherfunction`. On the other hand, if it ain't broke, ...
Andrew Jaffe
@Andrew: it might be a third-party code, to be honest.
SilentGhost
+3  A: 

The dict type has a get function, where if the key doesn't exist in the dictionary, the 2nd argument to get is the value that it should return. Similarly there is setdefault, which returns the value in the dict if the key exists, otherwise it sets the value according to your default parameter and then returns your default parameter.

You could extend the list type to have a getindexdefault method.

object SuperDuperList(list):
    def getindexdefault(self, elem, default):
        try:
            thing_index = self.index(elem)
        catch ValueError:
            return default

Which could then be used like:

mylist = SuperDuperList([0,1,2])
index = mylist.getindexdefault( 'asdf', -1 )
Ross Rogers
A: 

I don't know why you should think it is dirty... because of the exception? if you want a oneliner, here it is:

thing_index = thing_list.index(elem) if thing_list.count(elem) else -1

but i would advise against using it; I think Ross Rogers solution is the best, use an object to encapsulate your desiderd behaviour, don't try pushing the language to its limits at the cost of readability.

Alan Franzoni
Yes, because of the exception. Your code will do two linear searches won't it? Not that performance really matters here. The SuperDuperList solution is nice, but seems like overkill in this particular situation. I think I'll end up just catching the exception, but I wanted to see if there was a cleaner (to my aesthetic) way.
Draemon
@Draemon: well you'll encapsulate the code you have into the `find()` function and it will be all clean ;)
SilentGhost
+1  A: 

There is nothing wrong with your code that uses ValueError. Here's yet another one-liner if you'd like to avoid exceptions:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)
J.F. Sebastian
Is that python 2.6? I know I didn't mention it, but I'm using 2.5. This is probably what I'd do in 2.6
Draemon
@Draemon: Yes, `next()` function exists in Python 2.6+ . But it is easy to implement for 2.5, see [next() function implementation for Python 2.5](http://stackoverflow.com/questions/500578/is-there-an-alternative-way-of-calling-next-on-python-generators/500609#500609)
J.F. Sebastian
+1  A: 

This issue is one of language philosophy. In Java for example there has always been a tradition that exceptions should really only be used in "exceptional circumstances" that is when errors have happened, rather than for flow control. In the beginning this was for performance reasons as Java exceptions were slow but now this has become the accepted style.

In contrast Python has always used exceptions to indicate normal program flow, like raising a ValueError as we are discussing here. There is nothing "dirty" about this in Python style and there are many more where that came from. An even more common example is StopIteration exception which is raised by an iterator‘s next() method to signal that there are no further values.

Tendayi Mawushe
Actually, the JDK throws *way* too many checked exceptions, so I'm not sure that philosophy is actually applied to Java. I don't have a problem per-se with `StopIteration` because it's clearly-defined what the exception means. `ValueError` is just a little too generic.
Draemon
I was referring to the idea that exceptions should not be used for flow control: http://c2.com/cgi/wiki?DontUseExceptionsForFlowControl, not so much the number of checked exceptions that Java has which a whole other discussion: http://www.mindview.net/Etc/Discussions/CheckedExceptions
Tendayi Mawushe
A: 
thing_index = thing_list.index(elem) if elem in thing_list else -1

One line. Simple. No exceptions.

Emil Ivanov
Simple yes, but that will do two linear searches and while performance isn't an issue per-se, that seems excessive.
Draemon
@Draemon: Agree - that will do 2 passes - but it's unlikely that from a thousand-line code-base this one will be the bottleneck. :) One can always opt-in for an imperative solution with `for`.
Emil Ivanov
+1  A: 

What about this:

otherfunction(thing_collection, thing)

Rather than expose something so implementation-dependent like a list index in a function interface, pass the collection and the thing and let otherfunction deal with the "test for membership" issues. If otherfunction is written to be collection-type-agnostic, then it would probably start with:

if thing in thing_collection:
    ... proceed with operation on thing

which will work if thing_collection is a list, tuple, set, or dict.

This is possibly clearer than:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

which is the code you already have in otherfunction.

Paul McGuire