如何在一个python iterable上同时拥有多个迭代器?

如何在一个python iterable上同时拥有多个迭代器?,python,python-3.x,for-loop,combinatorics,itertools,Python,Python 3.x,For Loop,Combinatorics,Itertools,我想以组合方式比较我的iterable对象中的所有元素。下面的可复制示例只是模拟了普通列表的功能,但演示了我的问题。在这个例子中,有一个[“a”、“B”、“C”、“D”]的列表,我希望得到以下16行输出,每个项目的每个组合都是相互关联的。100个项目的列表应生成100*100=10000行 A A True A B False A C False ... 10 more lines ... D B False D C False D D True 下面的代码似乎应该完成这项工作 class C

我想以组合方式比较我的iterable对象中的所有元素。下面的可复制示例只是模拟了普通列表的功能,但演示了我的问题。在这个例子中,有一个[“a”、“B”、“C”、“D”]的列表,我希望得到以下16行输出,每个项目的每个组合都是相互关联的。100个项目的列表应生成100*100=10000行

A A True
A B False
A C False
... 10 more lines ...
D B False
D C False
D D True
下面的代码似乎应该完成这项工作

class C():
    def __init__(self):
        self.stuff = ["A","B","C","D"]
    def __iter__(self):
        self.idx = 0
        return self
    def __next__(self):
        self.idx += 1
        if self.idx > len(self.stuff):
            raise StopIteration
        else:
            return self.stuff[self.idx - 1]

thing = C()
for x in thing:
    for y in thing:
        print(x, y, x==y)
但在完成y形环之后,x形环似乎也完成了,尽管它只用于iterable中的第一项

A A True
A B False
A C False
A D False
经过多次搜索,我最终尝试了以下代码,希望itertools.tee允许我对相同的数据使用两个独立的迭代器:

import itertools
thing = C()
thing_one, thing_two = itertools.tee(thing)
for x in thing_one:
    for y in thing_two:
        print(x, y, x==y)
但是我得到了和以前一样的输出

它所代表的现实世界对象是一个目录和文件结构的模型,在树的不同深度有不同数量的文件和子目录。它具有指向数千个成员的嵌套链接,并在这些成员上正确地迭代一次,如本例所示。但它也会根据比较的需要在其许多内部对象中进行昂贵的动态处理,如果我必须在迭代之前制作一个完整的副本,这将导致工作量增加一倍。如果可能的话,我真的希望使用多个迭代器,指向一个包含所有数据的对象



编辑答案:所有答案中指出的问题代码中的关键缺陷是单个内部self.idx变量无法独立处理多个呼叫者。被接受的答案对我的真实类来说是最好的(在这个可复制的例子中过于简单),另一个答案为更简单的数据结构提供了一个简单、优雅的解决方案,如这里列出的列表。

您的
\uuuuuuuu iter\uuuuu
完全崩溃了。它不是在每次调用时实际生成一个新的迭代器,而是在
self
上重置一些状态并返回
self
。这意味着在对象上一次不能有多个迭代器,当对象上的另一个循环处于活动状态时,对
\uu iter\uu
的任何调用都会干扰现有的循环

您需要实际创建一个新对象。最简单的方法是使用
yield
语法编写生成器函数。生成器函数将在每次执行以下操作时自动返回一个新的迭代器对象:

class C(object):
    def __init__(self):
        self.stuff = ['A', 'B', 'C', 'D']
    def __iter__(self):
        for thing in self.stuff:
            yield thing

实际上,不可能创建一个容器类作为自己的迭代器。容器不应该知道迭代器的状态,迭代器也不需要知道容器的内容,它只需要知道哪个对象是对应的容器以及它在哪里。如果混合使用迭代器和容器,不同的迭代器将彼此共享状态(在您的情况下是
self.idx
),这将不会给出正确的结果(它们读取和修改相同的变量)

这就是为什么所有内置类型都有一个单独的迭代器类(甚至有些有一个反向迭代器类):

按预期打印16行

如果您的目标是创建自己的迭代器类,则需要两个类(如果您想自己实现反向迭代器,则需要3个类)

同样有效

为了完整起见,这里有一个可能的反向迭代器实现:

class C_reversed_iterator:
    def __init__(self, parent):
        self.parent = parent
        self.idx = len(parent.stuff) + 1
    def __iter__(self):
        return self
    def __next__(self):
        self.idx -= 1
        if self.idx <= 0:
            raise StopIteration
        else:
            return self.parent.stuff[self.idx - 1]

thing = C()
for x in reversed(thing):
    for y in reversed(thing):
        print(x, y, x==y)
或者显式地委托给一个生成器函数(这实际上等同于上述内容,但可能更清楚地表明它是一个生成的新对象):


注意:您不必实现
迭代器。这只是答案的附加“功能”。

无论是什么来源教你写这样的iterables,停止使用它。你的对象是可索引的吗?它有一个
\uuu len\uuuu
方法吗?它本质上是一个嵌套目录和文件结构的表示,所以对于多个级别,我没有一个单独的索引。不过,我确实有一个节点总数,所以我可以轻松地编写一个len。它嵌套的深度有多深?是否总是只有两个级别?每个家长的子女数量总是一致的还是不同的?或者可以动态地找到该数字?一般规则:如果定义了
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
,则
\uuuuuuuuuuu。如果不是,则代码是错误的。通常情况下,您并不想手动实现迭代器类,因此您只需将
\uuuuuuiter\uuuuu
作为一个生成器函数,如本文所示,并避免完全实现
\uuuuuuuu next\uuuu
。生成器函数方法比为您的类型定义一个单独的迭代器类更快(让Python管理生成器状态可以让它更高效地完成),也更简单。我需要弄清楚它们是否适用于我在对象中迭代多层次结构的用例。谢谢!我几乎可以肯定,我需要实现自己的迭代器,因为我的实际类需要一个接一个地迭代多个列表,对调用方来说是不透明的。我还在学习,但如果像收益率这样的发电机能够处理我的情况,我会感到惊讶。这个答案涵盖了让我到达我需要的地方的很多方面。干杯@mightypile:你说的一个接一个地迭代多个列表?
class C:
    def __init__(self):
        self.stuff = ["A","B","C","D"]
    def __iter__(self):
        return iter(self.stuff)

thing = C()
for x in thing:
    for y in thing:
        print(x, y, x==y)
class C:
    def __init__(self):
        self.stuff = ["A","B","C","D"]
    def __iter__(self):
        return C_iterator(self)
    def __reversed__(self):
        return C_reversed_iterator(self)

class C_iterator:
    def __init__(self, parent):
        self.idx = 0
        self.parent = parent
    def __iter__(self):
        return self
    def __next__(self):
        self.idx += 1
        if self.idx > len(self.parent.stuff):
            raise StopIteration
        else:
            return self.parent.stuff[self.idx - 1]

thing = C()
for x in thing:
    for y in thing:
        print(x, y, x==y)
class C_reversed_iterator:
    def __init__(self, parent):
        self.parent = parent
        self.idx = len(parent.stuff) + 1
    def __iter__(self):
        return self
    def __next__(self):
        self.idx -= 1
        if self.idx <= 0:
            raise StopIteration
        else:
            return self.parent.stuff[self.idx - 1]

thing = C()
for x in reversed(thing):
    for y in reversed(thing):
        print(x, y, x==y)
class C:
    def __init__(self):
        self.stuff = ["A","B","C","D"]
    def __iter__(self):
        yield from self.stuff
    def __reversed__(self):
        yield from self.stuff[::-1]
def C_iterator(obj):
    for item in obj.stuff:
        yield item

def C_reverse_iterator(obj):
    for item in obj.stuff[::-1]:
        yield item

class C:
    def __init__(self):
        self.stuff = ["A","B","C","D"]
    def __iter__(self):
        return C_iterator(self)
    def __reversed__(self):
        return C_reverse_iterator(self)