126

Python's list.index(x) throws an exception if the item doesn't exist. Is there a better way to do this that doesn't require handling exceptions?

Yarin
  • 173,523
  • 149
  • 402
  • 512
  • 1
    Depends. Do you care *where* it is? – Ignacio Vazquez-Abrams Nov 19 '11 at 21:04
  • 3
    The best way to do this depends on what you what to do in the case where nothing is found. Even if we had *list.find* that returned a -1 you would still need to test to see if the ``i == -1`` and take some action. – Raymond Hettinger Nov 19 '11 at 21:40
  • 3
    Raymond- just seems like it should be up to me to decide if my code can handle None indexes, rather than forcing the exception. But then, I'm still learning how to be Pythonic... – Yarin Nov 19 '11 at 21:58
  • possible duplicate of [Why list doesn't have safe "get" method like dictionary?](http://stackoverflow.com/questions/5125619/why-list-doesnt-have-safe-get-method-like-dictionary) – Samuel Katz Aug 06 '13 at 21:59
  • @yarin, as I clarified in my answer, if you don't need the index, the `in` keyword is what you need. But if you do, I think Raymond has a good point, and exceptions are the best way to approach this. – nealmcb Dec 17 '17 at 18:36
  • 3
    There are times when you know that there may be missing items and you just want to keep going with `None`. It's often useful for a missing index to throw, yes, but if `[][0]` throws, I would also like `[].index(0)` to return `None`, or at least allow `[].index(0, default=None)`. It's 1 line instead of 4. – NeilG Mar 16 '22 at 03:11
  • Python now has an assignment operator (something that could never happen, so everyone once thought)… which makes this even worse. – Jürgen A. Erhard Nov 26 '22 at 09:09

9 Answers9

125

If you don't care where the matching element is, then use:

found = x in somelist

If you do care, then use a LBYL style with a conditional expression:

i = somelist.index(x) if x in somelist else None
Yarin
  • 173,523
  • 149
  • 402
  • 512
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • 2
    Thanks Raymond, found this to be the most concise answer (Again, changed the default from -1 to None, as -1 is a valid list index) – Yarin Nov 19 '11 at 22:00
  • 2
    Thanks Raymond, LBYL was new reference for me. – Burhan Khalid Nov 20 '11 at 17:29
  • 30
    But isn't that approach much slower than with just running `index()`? After all you have to look twice: once for the existence and once for the index. This is why C++ containers don't have a `exists()`, only `find()`. – frans Jan 13 '16 at 11:09
  • 13
    As @frans said this requires two look-ups, but here is another way that can do the job in one pass: `i = next((i for i, t in enumerate(somelist) if x == t), None)` – AXO Oct 10 '16 at 10:18
  • 3
    @AXO Even though it's one pass, it's going to be slower for built-in types because the lookup is performed by Python rather than C code. – ivan_pozdeev Apr 08 '20 at 18:40
  • @ivan_pozdeev great point. Can someone test this? – jessexknight Feb 26 '22 at 04:00
10

implement your own index for list?

class mylist(list):
  def index_withoutexception(self,i):
    try:
        return self.index(i)
    except:
        return -1

So, you can use list, and with your index2, return what you want in case of error.

You can use it like this:

  l = mylist([1,2,3,4,5]) # This is the only difference with a real list
  l.append(4) # l is a list.
  l.index_withoutexception(19) # return -1 or what you want
A.H
  • 1,007
  • 5
  • 15
  • Beware as this can break some code: `type(l) == list` is `False` here. – bfontaine Jun 19 '14 at 13:37
  • 2
    Also - I'm not positive, but it seems to me that if the goal is to avoid raising exception (which is costly if it happens often), then this doesn't achieve it. It will return -1, but internally an exception will still be raised that will still be costly. – logicOnAbstractions Jun 08 '18 at 14:47
7

TL;DR: Exceptions are your friend, and the best approach for the question as stated.
It's easier to ask for forgiveness than permission (EAFP)

The OP clarified in a comment that for their use case, it wasn't actually important to know what the index was. As the accepted answer notes, using x in somelist is the best answer if you don't care.

But I'll assume, as the original question suggests, that you do care what the index is. In that case, I'll note that all the other solutions require scanning the list twice, which can bring a large performance penalty.

Furthermore, as the venerable Raymond Hettinger wrote in a comment

Even if we had list.find that returned a -1 you would still need to test to see if the i == -1 and take some action.

So I'll push back on the assumption in the original question that exceptions should be avoided. I suggest that exceptions are your friend. They're nothing to be scared of, they aren't inefficient, and in fact you need to be conversant with them to write good code.

So I think the best answer is to simply use a try-except approach:

try:
    i = somelist.index(x) 
except ValueError:
    # deal with it

"deal with it" just means do what you need to do: set i to a sentinel value, raise an exception of your own, follow a different code branch, etc.

This is an example of why the Python principle Easier to ask for forgiveness than permission (EAFP) makes sense, in contrast to the if-then-else style of Look before you leap (LBYL)

nealmcb
  • 12,479
  • 7
  • 66
  • 91
  • 3
    So true! I thought I had this problem, and at first thought one of the other solutions was good. Then I looked at my code again, and realized that using a *try...except* would be more quick and natural, since of course I still have to *deal with it*.... – nealmcb Dec 08 '19 at 17:44
  • 20
    I'm laughing out loud. Sometimes insights don't last. I just made the comment above, not noticing that I had written the answer in the first place 2 years ago. Only when I tried to vote up my own answer did SO clue me in. – nealmcb Dec 08 '19 at 17:47
  • 5
    exceptions are expensive – Andrew Scott Evans Feb 16 '21 at 06:56
  • @AndrewScottEvans Can you quantify the performance of exceptions, and compare the use of exceptions with other answers here? It is often said that premature optimization is a significant problem. So the inefficiency would have to be significant to warrant using an approach that was worse in other ways, unless the context is an inner loop. – nealmcb Feb 16 '21 at 23:25
  • you are calling an exception. that triggers an interrupt. – Andrew Scott Evans Feb 17 '21 at 22:32
  • @AndrewScottEvans Remember that the alternative is scanning the list twice, which can be arbitrarily expensive. And as noted at https://stackoverflow.com/a/2522013/507544 exceptions can be very fast, and even faster than the alternatives, though the details matter. So clearly the exception approach is faster for big lists. It would be great to see some examples analyzed, to help choose in situations were performance is critical. – nealmcb Feb 18 '21 at 20:31
  • 4
    @AndrewScottEvans You might be thinking of some other language. Python is not running native machine code, so it would not be necessary to implement exceptions as hardware interrupts. In the C API, an exception is a NULL return value and a globally set (per thread) exception object: https://docs.python.org/3/c-api/exceptions.html Arguably, the whole concept of a dynamically typed virtual machine is itself a performance hit, but given that, there's no reason for a Python exception to be fundamentally slower or faster than any other Python statement. – Jim Pivarski Mar 15 '21 at 23:22
6

Write a function that does what you need:

def find_in_iterable(x, iterable):
    for i, item in enumerate(iterable):
        if item == x:
            return i
    return None

If you only need to know whether the item exists, but not the index, you can use in:

x in yourlist
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 4
    PS '-1' is a valid list index- you need to return 'None' – Yarin Nov 19 '11 at 21:10
  • @Yarin: The reason I chose -1 was to be consistent with existing Python idioms, for example `'abc'.find('x') == -1`, but `None` would work too. I'll update my answer. – Mark Byers Nov 19 '11 at 21:11
  • 1
    Yeah, that's odd that .find() does that- seems inconsistent with Python's negative indexes. – Yarin Nov 19 '11 at 21:15
3

Yes, there is. You can eg. do something similar to this:

test = lambda l, e: l.index(e) if e in l else None

which works like that:

>>> a = ['a', 'b', 'c', 'g', 'c']
>>> test(a, 'b')
1
>>> test(a, 'c')
2
>>> test(a, 't')
None

So, basically, test() will return index of the element (second parameter) within given list (first parameter), unless it has not been found (in this case it will return None, but it can be anything you find suitable).

Tadeck
  • 132,510
  • 28
  • 152
  • 198
1

I like to use Web2py's List class, found in the storage module of its gluon package. The storage module offers list-like (List) and dictionary-like (Storage) data structures that do not raise errors when an element is not found.

First download web2py's source, then copy-paste the gluon package folder into your python installation's site-packages.

Now try it out:

>>> from gluon.storage import List
>>> L = List(['a','b','c'])
>>> print L(2)
c
>>> print L(3) #No IndexError!
None

Note, it can also behave like a regular list as well:

>>> print L[3]

Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
l[3]
IndexError: list index out of range
Himel Das
  • 1,148
  • 10
  • 13
1

If you don't care where it is in the sequence, only its presence, then use the in operator. Otherwise, write a function that refactors out the exception handling.

def inlist(needle, haystack):
  try:
    return haystack.index(needle)
  except ...:
    return -1
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • I have posted an answer that checks for existence using `in` and eventually returns the index, but I see you are aware of that method and decided to use handling exceptions that may be thrown. My question is: why did you chose catching exception instead of first checking `haystack` for existence of `needle`? Is there any reason for that? – Tadeck Nov 19 '11 at 21:27
  • @Tadeck: Because of the unwritten Python rule, "It is easier to ask forgiveness than permission". – Ignacio Vazquez-Abrams Nov 19 '11 at 21:29
  • 3
    Thanks :) I have just found it on Python glossary, where [**EAFP** (Easier to Ask for Forgiveness than for Permission)](http://docs.python.org/glossary.html#term-eafp) is shown as something opposite to [**LBYL** (Look Before You Leap)](http://docs.python.org/glossary.html#term-lbyl). And indeed EAFP has been named as _common Python coding style_, so it is not so unwritten :) Thanks again! – Tadeck Nov 19 '11 at 21:41
  • You don't really need the `...` in the `except ...:` line. – swdev Nov 03 '15 at 01:25
0

There is no built-in way to do what you want to do.

Here is a good post that may help you: Why list doesn't have safe "get" method like dictionary?

Community
  • 1
  • 1
juliomalegria
  • 24,229
  • 14
  • 73
  • 89
  • That post is about array indexing, with the `IndexError: list index out of range` problem, not the `index()` method with `ValueError: is not in list` issue described here. – nealmcb Dec 17 '17 at 18:00
0

hope this helps

lst= ','.join('qwerty').split(',') # create list
i='a'  #srch string
lst.index(i) if i in lst else None
blackwind
  • 182
  • 1
  • 10