273

I want an idiomatic way to find the first element in a list that matches a predicate.

The current code is quite ugly:

[x for x in seq if predicate(x)][0]

I've thought about changing it to:

from itertools import dropwhile
dropwhile(lambda x: not predicate(x), seq).next()

But there must be something more elegant... And it would be nice if it returns a None value rather than raise an exception if no match is found.

I know I could just define a function like:

def get_first(predicate, seq):
    for i in seq:
        if predicate(i): return i
    return None

But it is quite tasteless to start filling the code with utility functions like this (and people will probably not notice that they are already there, so they tend to be repeated over time) if there are built ins that already provide the same.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
fortran
  • 74,053
  • 25
  • 135
  • 175
  • 6
    Besides being asked later than "[python sequence find function](https://stackoverflow.com/questions/6039425/python-sequence-find-function)", this question has a **much better title**. – Wolf Jul 20 '17 at 10:23

4 Answers4

430

To find the first element in a sequence seq that matches a predicate:

next(x for x in seq if predicate(x))

Or simply:

Python 2:

next(itertools.ifilter(predicate, seq))

Python 3:

next(filter(predicate, seq))

These will raise a StopIteration exception if the predicate does not match for any element.


To return None if there is no such element:

next((x for x in seq if predicate(x)), None)

Or:

next(filter(predicate, seq), None)
Clint Eastwood
  • 4,995
  • 3
  • 31
  • 27
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • 37
    Or you can supply a second "default" argument to `next` that is used instead of raising the exception. – Karl Knechtel Dec 16 '11 at 12:50
  • 2
    @fortran: [`next()`](http://docs.python.org/library/functions.html#next) is available since Python 2.6 You could read [What's New page](http://docs.python.org/whatsnew/2.7.html) to quickly familiarize yourself with new features. – jfs Dec 16 '11 at 13:02
  • 1
    I am a python newbie and read the docs and the ifilter uses the "yield" method. I assume this means that the predicate is lazily evaluated as we go. i.e, we dont run the predicate through the entire list because I have a predicate function that is a bit expensive and I want to only iterate till the point where we find an item – Kannan Ekanath Nov 23 '12 at 13:43
  • 1
    @CalmStorm: yes. It iterates only until the item is found. – jfs Nov 23 '12 at 14:21
  • 1
    The fact that one variant uses `ifilter` and the other one does not is not helping. Please consider using generator expression in both for clarity. – Piotr Dobrogost Oct 02 '13 at 20:54
  • 1
    @PiotrDobrogost: I did it intentionally. If a predicate is a named function then `ifilter()` code contains less moving parts (better) and if the predicate is just a one-time expression then the genexpr is more suitable. So I've shown both variants. You're right: it's less clear because the two are not exactly equivalent, so it creates some confusion if a person doesn't know [what `ifilter()` does](http://docs.python.org/2/library/itertools.html#itertools.ifilter). Those that don't want to be forced to learn can read [the very next answer by @Chewie](http://stackoverflow.com/a/8534391/4279). – jfs Oct 09 '13 at 14:27
  • This is a bad solution as it continues searching even after it finds a match. – Charlie Jan 22 '15 at 22:04
  • @Charlie: it does *not* – jfs Jan 22 '15 at 22:05
  • @J.F.Sebastian based on the docs, the list comprehension returns a list. From what I can read, there's no indication this list is lazily populated as the iterator progresses. – Charlie Jan 22 '15 at 23:04
  • @Charlie: that is why the solution uses genexpr, not listcomp – jfs Jan 22 '15 at 23:06
  • @J.F.Sebastian I see now, I wasn't aware of generator expressions. For Anyone else looking for more info: https://www.python.org/dev/peps/pep-0289/ – Charlie Jan 25 '15 at 22:34
  • 1
    ...and that's where I love ruby `seq.find {|x| predicate(x)}`. – finiteautomata Jul 20 '15 at 04:40
  • 2
    @geekazoid: `seq.find(&method(:predicate))` or even more concise for instance methods e.g.: `[1,1,4].find(&:even?)` – jfs Jul 20 '15 at 05:07
  • 16
    `ifilter` was renamed to `filter` in Python 3. – tsauerwein Mar 23 '16 at 10:56
  • This exactly what I was looking for. Thanks. Sometimes, in Python, it can be hard to figure out how to accomplish these things. – Martin Mar 30 '21 at 15:16
104

You could use a generator expression with a default value and then next it:

next((x for x in seq if predicate(x)), None)

Although for this one-liner you need to be using Python >= 2.6.

This rather popular article further discusses this issue: Cleanest Python find-in-list function?.

Chewie
  • 7,095
  • 5
  • 29
  • 36
11

I don't think there's anything wrong with either solutions you proposed in your question.

In my own code, I would implement it like this though:

(x for x in seq if predicate(x)).next()

The syntax with () creates a generator, which is more efficient than generating all the list at once with [].

mac
  • 42,153
  • 26
  • 121
  • 131
  • And not only that - with `[]` you might run into problems if the iterator never ends or its elements are hard to create, the later it gets... – glglgl Dec 16 '11 at 12:54
  • 7
    `'generator' object has no attribute 'next'` on Python 3. – jfs Dec 16 '11 at 12:57
  • @glglgl - As for the first point (never ends) I doubt it, as the argument is a finite sequence [more precisely a list according to the OP' question]. As for the second: again, since the argument supplied is a sequence, the objects should have already been created and stored by the time this function is called.... or am I missing something? – mac Dec 16 '11 at 12:58
  • @J.F.Sebastian - Thank you, I wasn't aware of that! :) Out of curiosity, what's the design principle behind this choice? – mac Dec 16 '11 at 12:58
  • @mac - For consistency with the double underscore of other special methods. See http://www.python.org/dev/peps/pep-3114/ – Chewie Dec 16 '11 at 13:13
  • @mac For the given case you are right - but in the general case where those restrictions are not present, problems can occur. – glglgl Dec 16 '11 at 13:27
3

J.F. Sebastian's answer is most elegant but requires python 2.6 as fortran pointed out.

For Python version < 2.6, here's the best I can come up with:

from itertools import repeat,ifilter,chain
chain(ifilter(predicate,seq),repeat(None)).next()

Alternatively if you needed a list later (list handles the StopIteration), or you needed more than just the first but still not all, you can do it with islice:

from itertools import islice,ifilter
list(islice(ifilter(predicate,seq),1))

UPDATE: Although I am personally using a predefined function called first() that catches a StopIteration and returns None, Here's a possible improvement over the above example: avoid using filter / ifilter:

from itertools import islice,chain
chain((x for x in seq if predicate(x)),repeat(None)).next()
parity3
  • 643
  • 9
  • 18
  • 19
    Yikes! if it comes down to that, I would just do the simple "for" loop with an "if" inside it -- much easier to read – Nick Perkins Aug 29 '12 at 14:31