您尝试做的事情并非完全不可能,只是很复杂,而且可能很浪费。
如果您想将一个可迭代对象划分为两个可迭代对象,并且源是一个列表或其他可重复使用的可迭代对象,那么您最好分两遍进行,就像您的问题一样。
即使源是一个迭代器,如果你想要的输出是一对列表,而不是一对惰性迭代器,要么使用Martijn's answer,要么对list(iterator)进行两次传递。)
但是,如果您确实需要将任意可迭代对象延迟划分为两个可迭代对象,那么如果没有某种中间存储,就无法做到这一点。
假设您将[1, 2, -1, 3, 4, -2] 划分为positives 和negatives。现在你尝试next(negatives)。那应该给你-1,对吧?但是如果不使用1 和2,它就无法做到这一点。这意味着当您尝试next(positives) 时,您将获得3 而不是1。因此,1 和 2 需要存储在某个地方。
您需要的大部分聪明才智都包含在 itertools.tee 中。如果你只是将 positives 和 negatives 制作成同一个迭代器的两个 teed 副本,然后过滤它们,你就完成了。
事实上,这是itertools 文档中的配方之一:
def partition(pred, iterable):
'Use a predicate to partition entries into false entries and true entries'
# partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9
t1, t2 = tee(iterable)
return filterfalse(pred, t1), filter(pred, t2)
(如果您不能理解这一点,可能值得明确地写出来,两个生成器函数通过闭包共享一个迭代器和一个 tee,或者一个类的两个方法通过 self 共享它们。它应该是几十行不需要任何棘手的代码。)
您甚至可以从 more_itertools 等第三方库导入 partition。
现在,您可以在单行中使用它:
lst = [1, 2, -1, 3, 4, -2]
positives, negatives = partition(lst, lambda x: x>=0)
... 你有一个遍历所有正值的迭代器和一个遍历所有负值的迭代器。它们看起来像是完全独立的,但它们一起只对lst 进行一次传递——因此即使您将lst 分配给生成器表达式或文件或其他东西而不是列表,它也可以工作。
那么,为什么没有某种快捷语法呢?因为这会很误导人。
推导不需要额外的存储空间。这就是生成器表达式如此出色的原因——它们可以将一个惰性迭代器转换为另一个惰性迭代器,而无需存储任何内容。
但这需要O(N) 存储。想象一下所有的数字都是正数,但你首先尝试迭代negative。发生什么了?所有号码都被推送到trueq。事实上,O(N) 甚至可能是无限(例如,在 itertools.count() 上试试)。
这对于itertools.tee 之类的东西很好,这是一个卡在模块中的函数,大多数新手甚至都不知道,它有很好的文档可以解释它的作用并明确成本。但是如果使用语法糖让它看起来像正常的理解,那就是另一回事了。