如何根据条件打破列表理解,例如当找到数字
412
时?
代码:
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]
所以从功能上来说,你可以推断这是应该做的:
even = [n for n in numbers if 0 == n % 2 and break if n == 412]
我真的更喜欢:
import
语句或类似语句)使用函数来举起
StopIteration
和 list
来抓住它:
>>> 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]
对于那些抱怨的人来说,这不是一句简单的话:
even = list(next(iter(())) if n == 412 else n for n in numbers if 0 == n % 2)
对于那些抱怨它很黑客并且不应该在生产代码中使用的人:嗯,你是对的。 当然。
itertools.takewhile()
: 一起使用
even_numbers = (n for n in numbers if not n % 2)
list(itertools.takewhile(lambda x: x != 412, even_numbers))
编辑:我刚刚注意到不使用任何
import
的要求。好吧,无论如何我把这个答案留在这里。
我知道这是一篇非常旧的帖子,但是由于OP询问如何在
break
中使用list-comprehension
并且我也在寻找类似的东西,我想我会在这里发布我的发现以供将来参考。
在调查
break
时,我遇到了iter
鲜为人知的功能,即iter(callable, sentinel)
,它返回一个迭代器,一旦可调用function
值等于sentinel
值,“中断”迭代。
>>> 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.
这里棘手的部分是定义一个适合给定问题的函数。在这种情况下,首先我们需要使用
list
将给定的 numbers
的 iterator
转换为 x = iter(numbers)
,将其作为外部变量输入到 lambda
函数中。
接下来,我们的可调用函数只是调用迭代器来吐出下一个值。然后迭代器与我们的哨兵值(在本例中为 412)进行比较,并在达到该值后“中断”。
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]
even = [n for n in numbers[:None if 412 not in numbers else numbers.index(412)] if not n % 2]
刚刚采用了 F.J. 上面的代码并添加了一个三元组来检查 412 是否在列表中。仍然是“单行”,即使 412 不在列表中也可以工作。
如果 412 肯定会出现在列表中,你可以使用这个:
even = [n for n in numbers[:numbers.index(412)] if not n % 2]
如果您想在结果中包含 412,只需使用
numbers[:numbers.index(412)+1]
作为切片。
请注意,由于切片的原因,这将比 itertools 或 for 循环解决方案效率低(至少在内存方面)。
列表显示的语法(包括列表推导式)位于:http://docs.python.org/reference/expressions.html#list-displays
如您所见,没有特殊的
while
或 until
语法。您能得到的最接近的是:
even_numbers = (n for n in numbers if 0 == n % 2)
list(itertools.takewhile(lambda x: x != 412, even_numbers))
(代码取自 Sven Marnach 的答案,在我打字时发布)。
有一次我在SO上遇到类似的问题,答案是:
next((x for x in [1, 2, 3, 4] if x % 2 == 0), [])
最后一个
[]
需要默认,以防止找不到时出现StopIteration
错误
或
any(print(x) if x < 2 else True for x in range(5))
print
为了证明,他返回None
(逻辑上False
)。
另一个偷偷摸摸的单行解决方案,在
breaking in list comprehension
条件的帮助下解决 end
。
不使用
numbers.index(412)
,也许会快一点?
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]
注意:这是一个坏主意。 只是为了好玩:)
正如@WolframH 所说:
对于那些抱怨它很黑客并且不应该在生产代码中使用的人:嗯,你是对的。 当然。
考虑到生成器解决方案已经过时,我提出了以下方案:
even = [n for n in next((numbers[:i] for i, n in enumerate(numbers) if n == 412)) if not n % 2]
然后我回去看到安德鲁克拉克的答案,它是相同的但更好
even = [n for n in numbers[:numbers.index(412)] if not n % 2]
无论如何,切片解决方案的最佳部分是您可以选择包含或排除结束元素两侧的多个元素,例如获取 412 和后面的数字:
even = [n for n in numbers[:numbers.index(412)+2] if not n % 2]
我遇到了同样的问题打破列表理解。正如许多人所说,自 Python 3.5 的 PEP 479 以来,得票最高的解决方案已经过时:从生成器内部抛出的
StopIteration
将被转换为 RuntimeError
,而不是结束生成器本身。
我目前使用的是 Python 3.11.4,所有其他解决方案(列表切片、
itertools.takewhile
、短路 any
、断路器 iter
和 @recnac 的 end
)仍然有效.
我发现
takewhile
是最优雅和Pythonic的,但是不知怎的,我想写一些被诅咒的单行,它实际上突破了列表而不是跳过列表的其余部分,但这似乎是无法实现的。