7

I want to stop the iteration when I find the first True, like this:

>> for x in ['a', 'b', 1, 2, 3]:
>>    if isinstance(x, int):
>>        print(x)
>>        break

 1

How can I do the same thing but with list comprehension for-loop?

igorkf
  • 3,159
  • 2
  • 22
  • 31
  • 1
    Possible duplicate of [break list comprehension](https://stackoverflow.com/questions/9572833/break-list-comprehension) – pissall Nov 10 '19 at 03:41
  • 1
    While that is possible, it is going to be much less beautiful than a loop. – Klaus D. Nov 10 '19 at 03:45
  • 1
    A [list comprehension](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) is a succinct way of creating a `list`, not searching though one for something until it is found and then printing it out — which is what your code is doing. Please be more specific about what you want. – martineau Nov 10 '19 at 12:24

2 Answers2

10

You can use next on a generator expression to return the first True instance:

x = next((a for a in ['a', 'b', 1, 2, 3] if isinstance(a, int)), None)
print(x)
1

x = next((a for a in ['a', 'b'] if isinstance(a, int)), None)
print(x)
None

Where the second argument is a return value that allows you to avoid StopIteration errors

Edit (credit @martineau)

This can be reduced to the filter operator as shown by the itertools recipe above:

x = next(filter(int.__instancecheck__, ['a', 'b', 1, 2, 3]), None)
C.Nivs
  • 12,353
  • 2
  • 19
  • 44
  • Your code could be shortened since a large portion of it is equivalent to what the built-in [`filter()`](https://docs.python.org/3/library/functions.html#filter) function does — which is probably why the `itertools` [recipe](https://docs.python.org/3/library/itertools.html#itertools-recipes) named `first_true()` uses it. – martineau Nov 10 '19 at 12:42
  • True, a `next(filter(...))` would probably be faster here – C.Nivs Nov 10 '19 at 17:43
5

Unfortunately, there is no built-in mechanism for finding the first element of a list that matches a predicate. There is any, which you can use to determine if such an element exists, but that won't tell you what element it was.

any(isinstance(x, int) for x in ['a', 'b', 1, 2, 3])

Luckily, itertools provides a variety of functions for when you want to do some operation on a list or any other iterable.

The function you're looking for isn't built in to the module, but the documentation provides definitions for a handful of functions, including this one, as the itertools recipes. The function you need is named first_true. Just copy the definition, and:

def first_true(iterabe, default=False, pred=None):
    return next(filter(pred, iterable), default)

x = first_true(['a', 'b', 1, 2, 3], pred=int.__instancecheck__)

If nothing in the list matches the predicate, first_true will return False. If you want a different behavior, you can modify the function. The other recipes might be a good place to start if you want to understand how to work with iterators in the itertools style.


If you're trying to do more complicated processing in the loop than just finding the first matching element, you're probably better off using a proper for loop, though. List comprehensions and generator expressions are good as a replacement for map and filter, and itertools provides more general operations on iterables, but they aren't meant to stand in for real loop logic.

jirassimok
  • 3,850
  • 2
  • 14
  • 23
  • I'm getting `NameError: name 'first_true' is not defined` – igorkf Nov 10 '19 at 03:49
  • 1
    You need to define the `first_true` function, using the definition in the list at the link. You'll need to import a few functions from `itertools` to do so. (I'd include the definition here, but I don't own the copyright to it.) – jirassimok Nov 10 '19 at 03:56
  • @jirassimok: Python is open source, so don't worry about copying code from it, especially from example its documentation (as long a you cite the source like you're doing). – martineau Nov 10 '19 at 05:41
  • @martineau I'm aware; it should fall under fair use. I'm not sure what else I was thinking of when I wrote that, actually. – jirassimok Nov 10 '19 at 07:01
  • Although defining a function according to the `itertools` recipe would more elegant and readable, it's so short one could simply use it — i.e. `x = next(filter(int.__instancecheck__, ['a', 'b', 1, 2, 3]), None)`. – martineau Nov 10 '19 at 12:18
  • You do not "need to import a few functions from `itertools`" first — `next()` and `filter()` are built-ins. – martineau Nov 10 '19 at 12:29
  • @martineau Oh, duh. I don't think I actually looked at the definition when I wrote the answer. Updated. – jirassimok Nov 10 '19 at 17:36