29

Why does list.index throw an exception, instead of using an arbitrary value (for example, -1)? What's the idea behind this?

To me it looks cleaner to deal with special values, rather than exceptions.

EDIT: I didn't realize -1 is a potentially valid value. Nevertheless, why not something else? How about a value of None?

dreftymac
  • 31,404
  • 26
  • 119
  • 182
Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • 3
    Just one comment: everybody says -1 is a valid index, which is true, but that doesn't mean it still couldn't have been used a "not found" result from index(). It's not as if index() would *ever* return -1 in normal operations. I find it frustrating that I have to write a whole try…except block, and absorb the overhead of handling an exception in order to deal with what is *not* necessarily an error condition. – Edward Falk Feb 13 '20 at 05:55

11 Answers11

19

Because -1 is itself a valid index. It could use a different value, such as None, but that wouldn't be useful, which -1 can be in other situations (thus str.find()), and would amount simply to error-checking, which is exactly what exceptions are for.

Devin Jeanpierre
  • 92,913
  • 4
  • 55
  • 79
19

Well, the special value would actually have to be None, because -1 is a valid index (meaning the last element of a list).

You can emulate this behavior by:

idx = l.index(x) if x in l else None
vartec
  • 131,205
  • 36
  • 218
  • 244
  • 3
    well, it's that or try/except. – vartec Mar 24 '09 at 08:40
  • 2
    It does seem at times that Python has been written so you write as much code as possible for the most simple things. Thanks for the answer! – PeterS Apr 28 '20 at 14:50
  • actually try/catch alternative looks faster, specially for worst case, as can be seen in [this answer](https://stackoverflow.com/a/13161008/4913143). I also verified this on python 3.8.5 – Rodrigo Laguna May 15 '22 at 12:26
9

To add to Devin's response: This is an old debate between special return values versus exceptions. Many programming gurus prefer an exception because on an exception, I get to see the whole stacktrace and immediate infer what is wrong.

amit kumar
  • 20,438
  • 23
  • 90
  • 126
  • 2
    This was what I was wondering: special return values versus exceptions. – Joan Venge Mar 23 '09 at 17:06
  • 5
    +1: Exceptions don't depend on some value being "sacred". Exceptions are often simpler to work with. – S.Lott Mar 23 '09 at 18:21
  • The exceptiophobic folks might argue that `index` not finding an object is not an error, and exceptions should only be used on errors. I am for exceptions in this case. Exceptions can be faster since they don't have to do a check on success. Python prefers exceptions in many cases due to this. In other words, if `index` finds an object, execution continues with no condition check. If `index` finds nothing, you are thrown into the catch block with no checks. – Cory-G Jun 23 '14 at 23:24
  • 2
    The last comment ignores the fact that the underlying function of course requires to check for the condition(s) that make it _fail_. The additional check for the special value is negligible. The bigger question is indeed if not finding anything is an error. this of course depends on the use case. However, this is a low-level function that should not mandate any specific use by making it necessary to rely on slow exception handling hence this is clearly bad design because the only alternative is to re-implement it or use even more performance-reducing techniques like checking with count first. – stefanct Dec 06 '15 at 19:25
  • Using exceptions for flow control is both slow, and a bad practice in general. – A.R. May 05 '22 at 13:45
  • @A.R. Python convention in this regard is different from C. Check this: https://stackoverflow.com/a/11360880/19501 – amit kumar May 09 '22 at 03:23
  • @amitkumar making an anti-pattern a convention doesn't make it less of an anti-pattern. – A.R. May 16 '22 at 14:36
  • @A.R. It's a language design choice to be "slow" to make it easy to use; Python is now the most popular language in many fields where non-programmers program, such as science, where its ease of use saves much more societal resources (scientist's paychecks) than its performance would save energy costs. Calling something an unconditional anti-pattern is a design anti-pattern ;) – Robin De Schepper Sep 28 '22 at 10:48
3

It's mainly to ensure that errors are caught as soon as possible. For example, consider the following:

l = [1, 2, 3]
x = l.index("foo")  #normally, this will raise an error
l[x]  #However, if line 2 returned None, it would error here

You'll notice that an error would get thrown at l[x] rather than at x = l.index("foo") if index were to return None. In this example, that's not really a big deal. But imagine that the third line is in some completely different place in a million-line program. This can lead to a bit of a debugging nightmare.

Jason Baker
  • 192,085
  • 135
  • 376
  • 510
3

The "exception-vs-error value" debate is partly about code clarity. Consider code with an error value:

idx = sequence.index(x)
if idx == ERROR:
    # do error processing
else:
    print '%s occurred at position %s.' % (x, idx)

The error handling ends up stuffed in the middle of our algorithm, obscuring program flow. On the other hand:

try:
    idx = sequence.index(x)
    print '%s occurred at position %s.' % (x, idx)
except IndexError:
    # do error processing

In this case, the amount of code is effectively the same, but the main algorithm is unbroken by error handling.

John Fouhy
  • 41,203
  • 19
  • 62
  • 77
  • Using exceptions for flow control is a well known bad practice. Yes, python forces it on us in this case, but while the two code blocks in your answer are "effectively the same", they have side effects that are very different, especially if you did this in another language like C++/Java/C#/etc. – A.R. May 05 '22 at 13:50
  • Flow control is no known to be a bad practice by the python language designers. Every time a `for` loop terminates, it uses a thrown exception. This is by design. That is definitely flow control, and it's built into the language. In some languages, the idiomatic style discourages exceptions for flow control, but not python. – recursive May 06 '22 at 17:09
2

In Python, -1 is a valid index, meaning a number from the end of the list (instead of the beginning), so if you were to do

idx = mylist.index(someval)
nextval = mylist[idx+1]

then you would get the first value of the array, instead of realising there was an error. This way you can catch the exception and deal with it. If you don't want exceptions, then just check beforehand:

if someval in mylist:
    idx = mylist.index(someval)

Edit: There's not really any point in returning None, because if you're going to test for None, you might as well test whether the value is in the list as above!

Phil H
  • 19,928
  • 7
  • 68
  • 105
1
def GetListIndex(list, searchString):
    try:
        return list.index(searchString)

    except ValueError:
        return False

    except Exception:
        raise
Mawg says reinstate Monica
  • 38,334
  • 103
  • 306
  • 551
1

I agree with Devin Jeanpierre, and would add that dealing with special values may look good in small example cases but (with a few notable exceptions, e.g. NaN in FPUs and Null in SQL) it doesn't scale nearly as well. The only time it works is where:

  • You've typically got lots of nested homogeneous processing (e.g. math or SQL)
  • You don't care about distinguishing error types
  • You don't care where the error occurred
  • The operations are pure functions, with no side effects.
  • Failure can be given a reasonable meaning at the higher level (e.g. "No rows matched")
MarkusQ
  • 21,814
  • 3
  • 56
  • 68
0

One simple idea: -1 is perfectly usable as an index (as are other negative values).

gimel
  • 83,368
  • 10
  • 76
  • 104
0

It's a semantic argument. If you want to know the index of an element, you are claiming that it already exists in the list. If you want to know whether or not it exists, you should use in.

recursive
  • 83,943
  • 34
  • 151
  • 241
0

Specified here is I think quite a good way which simply returns [] where no matches are found.

mylist = ['a','b','abcde', 'a']
searchstring = 'ss'
[i for i, x in enumerate(mylist) if x == searchstring]
gaut
  • 5,771
  • 1
  • 14
  • 45