如何反转itertools.chain对象?

11
我的函数创建了一个生成器链:
def bar(num):
    import itertools
    some_sequence = (x*1.5 for x in range(num))
    some_other_sequence = (x*2.6 for x in range(num))
    chained = itertools.chain(some_sequence, some_other_sequence)
    return chained

我的函数有时需要以相反的顺序返回 chained 值。从概念上讲,以下是我想要实现的:

if num < 0:
    return reversed(chained)
return chained

不幸的是:

>>> reversed(chained)
TypeError: argument to reversed() must be a sequence

我的选择有哪些?

由于这是实时图形渲染代码,所以我不想让它变得太复杂/缓慢。

编辑: 当我最初提出这个问题时,我没有考虑生成器的可逆性。正如许多人指出的那样,生成器不能被反转。

事实上,我确实希望反转链的扁平内容;而不仅仅是生成器的顺序。

根据回答,我无法使用单个调用来反转itertools.chain,因此我认为在反转情况下,唯一的解决方案是使用列表,或者可能同时使用列表。


嗯,我本以为使用负步长的 itertools.islice 可以解决问题,但事实证明该参数只接受正值。这是一个有趣的问题。 - Daniel DiPaolo
不仅仅是“chained”,而是你的生成器: >>> reversed((x for x in range(5))) TypeError: argument to reversed() must be a sequence - Josh Lee
@jleedev 很好的观点;即使有一种方法可以反转itertools.chain,生成器也无法被反转。我以前没有意识到这一点,但现在明白了。然而,reversed也不能用于由列表组成的itertools.chain。 - Steven T. Snyder
7个回答

12
if num < 0:
    lst = list(chained)
    lst.reverse()
    return lst
else:
    return chained

reversed()需要一个真正的序列,因为它通过索引倒序迭代该序列,对于生成器不起作用(它只有“下一个”项的概念)。

既然你需要将整个生成器展开以进行反转,最有效的方法是将其读入列表,并使用.reverse()方法原地翻转列表。


7
是的,但如果您需要在通用情况下访问最后一个项目(这是通过反转隐含的),则必须耗尽生成器。如果没有看到真实的代码,则无法提供更好的解决方案。 - shang
1
正如Ant、Jochen Ritzel等人所提出的,生成器无法在不用完它们的情况下被反转。当我提出最初的问题时,我没有考虑到这一点。至少shang的解决方案只针对num < 0条件展开生成器。 - Steven T. Snyder

11

按照定义,你不能反向生成器。生成器的接口是迭代器,它只支持正向迭代的容器。当您想要反向迭代器时,必须先收集所有项,然后将它们反转。

相反,建议使用列表或从开头开始反向生成序列。


4

itertools.chain需要实现__reversed__()(这是最好的方法)或__len__()__getitem__()

由于它没有实现,而且甚至没有一种方法可以访问内部序列,所以您需要扩展整个序列才能将其翻转。

reversed(list(CHAIN_INSTANCE))

如果所有的序列都是可逆的,那么如果chain能够提供__reversed__()方法就更好了,但目前还没有实现。也许你可以编写自己的chain版本来实现这个功能。


1
def reversed2(iter):
    return reversed(list(iter))

0

reversed 只适用于支持 len 和索引的对象。在将其包裹在 reversed 前,您必须首先生成生成器的所有结果。

然而,您可以轻松地执行以下操作:

def bar(num):
    import itertools
    some_sequence = (x*1.5 for x in range(num, -1, -1))
    some_other_sequence = (x*2.6 for x in range(num, -1, -1))
    chained = itertools.chain(some_other_sequence, some_sequence)
    return chained

实现 __reversed__ 方法的对象也可以与 reversed() 一起使用:https://docs.python.org/2/reference/datamodel.html#object.__reversed__ - Hubert Kario

0

这个在你的真实应用中能工作吗?

def bar(num):
    import itertools
    some_sequence = (x*1.5 for x in range(num))
    some_other_sequence = (x*2.6 for x in range(num))
    list_of_chains = [some_sequence, some_other_sequence]
    if num < 0:
        list_of_chains.reverse()
    chained = itertools.chain(*list_of_chains)
    return chained

3
这会颠倒链中序列的顺序,而不是序列本身。假设有L1 = [1,2,3]; L2 = [4,5,6],把它们连接起来并翻转结果是[6,5,4,3,2,1],使用你提供的解决方案得到的结果将是[4,5,6,1,2,3],对吗? - Daniel DiPaolo
2
我认为这就是 OP 的意思。没有办法在不耗尽生成器的情况下反转返回的值的顺序。 - Ant
1
谢谢您的建议。当我提出原始问题时,我没有考虑到生成器无法反转,因此实际上我想要反转链的扁平内容而不是链本身的生成器顺序。 - Steven T. Snyder

0

从理论上来说,你不能这样做,因为链式对象甚至可能包含无限序列,比如 itertools.count(...)

如果可能的话,你应该尝试反转你的生成器/序列或者针对每个序列使用reversed(iterable),然后从后往前将它们链接起来。当然,这高度取决于你的使用场景。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接