Reputation: 13816
How can I break a list comprehension based on a condition, for instance when the number 412
is found?
Code:
numbers = [951, 402, 984, 651, 360, 69, 408, 319, 601, 485, 980, 507, 725, 547, 544,
615, 83, 165, 141, 501, 263, 617, 865, 575, 219, 390, 984, 592, 236, 105, 942, 941,
386, 462, 47, 418, 907, 344, 236, 375, 823, 566, 597, 978, 328, 615, 953, 345, 399,
162, 758, 219, 918, 237, 412, 566, 826, 248, 866, 950, 626, 949, 687, 217, 815, 67,
104, 58, 512, 24, 892, 894, 767, 553, 81, 379, 843, 831, 445, 742, 717, 958, 609, 842,
451, 688, 753, 854, 685, 93, 857, 440, 380, 126, 721, 328, 753, 470, 743, 527]
even = [n for n in numbers if 0 == n % 2]
So functionally, it would be something you can infer this is supposed to do:
even = [n for n in numbers if 0 == n % 2 and break if n == 412]
I really prefer:
import
statement or similar)Upvotes: 90
Views: 81235
Reputation: 1
I came across the same very problem to breaking the list comprehension. And as many says, the highest voted solution is outdated since Python 3.5 for PEP 479: A StopIteration
thrown from inside of a generator will be cast into an RuntimeError
, instead of ending the generator itself.
I'm currently in Python 3.11.4, and all other solutions (list slicing, itertools.takewhile
, short-circuiting any
, the breaker iter
, and the end
thing by @recnac) still works.
I find takewhile
the most elegant and pythonic, however somehow I want to write some cursed one-liner, that actually breaks out of the list instead of skipping through the rest of the list, but that seems to be unfulfillable.
Upvotes: 0
Reputation: 601609
You can use generator expressions together with itertools.takewhile()
:
even_numbers = (n for n in numbers if not n % 2)
list(itertools.takewhile(lambda x: x != 412, even_numbers))
Edit: I just noticed the requirement not to use any import
s. Well, I leave this answer here anyway.
Upvotes: 71
Reputation: 417
Once I met a similar question on SO, the answer was:
next((x for x in [1, 2, 3, 4] if x % 2 == 0), [])
The last []
needs as default to prevent the StopIteration
error if not found
or
any(print(x) if x < 2 else True for x in range(5))
print
to prove, he returns None
(logically False
).
Upvotes: 1
Reputation: 39
Considering the generator solution is outdated I came up with the following:
even = [n for n in next((numbers[:i] for i, n in enumerate(numbers) if n == 412)) if not n % 2]
Then I went back and saw Andrew Clark's answer which is the same but much better
even = [n for n in numbers[:numbers.index(412)] if not n % 2]
Regardless the best part about the slicing solution is you can choose to include or exclude a number of elements on either side of the ending element for example to get 412 and the number after:
even = [n for n in numbers[:numbers.index(412)+2] if not n % 2]
Upvotes: 0
Reputation: 3744
another sneaky one-line solution to solve breaking in list comprehension
, with the help of end
condition.
without using numbers.index(412)
, maybe a little bit faster?
even = [n for end in [[]] for n in numbers
if (False if end or n != 412 else end.append(42))
or not end and not n % 2]
Note: This is a bad idea. just for fun : )
as @WolframH said:
For those complaining it is hackish and should not be used in production code: Well, you're right. Definitely.
Upvotes: 0
Reputation: 11453
I know this is a VERY OLD post, however since OP asked about using break
inside a list-comprehension
and I was also looking for something similar, I thought I would post my findings here for future reference.
While investigating break
, I came across little known feature of iter
as iter(callable, sentinel)
which return an iterator that "breaks" iteration once callable function
value is equal to sentinel
value.
>>> help(iter)
Help on built-in function iter in module __builtin__:
iter(...)
iter(collection) -> iterator
iter(callable, sentinel) -> iterator
Get an iterator from an object. In the first form, the argument must
supply its own iterator, or be a sequence.
In the second form, the callable is called until it returns the sentinel.
Tricky part here is defining a function that would fit given problem. In this case first we need to convert given list
of numbers
to an iterator
using x = iter(numbers)
which feeds as external variable into lambda
function.
Next, our callable function is just a call to the iterator to spit out next value. The iterator then compares with our sentinel value (412 in this case) and "breaks" once that value is reached.
print [i for i in iter(lambda x=iter(numbers): next(x),412) if i %2 == 0]
>>>
[402, 984, 360, 408, 980, 544, 390, 984, 592, 236, 942, 386, 462, 418,
344, 236, 566, 978, 328, 162, 758, 918]
Upvotes: 28
Reputation: 3071
even = [n for n in numbers[:None if 412 not in numbers else numbers.index(412)] if not n % 2]
Just took F.J.'s code above and added a ternary to check if 412 is in the list. Still a 'one liner' and will work even if 412 is not in the list.
Upvotes: 20
Reputation: 4723
Use a function to raise StopIteration
and list
to catch it:
>>> def end_of_loop():
... raise StopIteration
...
>>> even = list(end_of_loop() if n == 412 else n for n in numbers if 0 == n % 2)
>>> print(even)
[402, 984, 360, 408, 980, 544, 390, 984, 592, 236, 942, 386, 462, 418, 344, 236, 566, 978, 328, 162, 758, 918]
For those complaining it is not a one-liner:
even = list(next(iter(())) if n == 412 else n for n in numbers if 0 == n % 2)
For those complaining it is hackish and should not be used in production code: Well, you're right. Definitely.
Upvotes: 79
Reputation: 49826
The syntax for list displays (including list comprehensions) is here: http://docs.python.org/reference/expressions.html#list-displays
As you can see, there is no special while
or until
syntax. The closest you can get is:
even_numbers = (n for n in numbers if 0 == n % 2)
list(itertools.takewhile(lambda x: x != 412, even_numbers))
(Code taken from Sven Marnach's answer, posted while I was typing this).
Upvotes: 3
Reputation: 208475
If 412 will definitely be in the list you could use this:
even = [n for n in numbers[:numbers.index(412)] if not n % 2]
If you want to include 412 in the result just use numbers[:numbers.index(412)+1]
for the slice.
Note that because of the slice this will be less efficient (at least memory-wise) than an itertools or for loop solution.
Upvotes: 15