为什么生成器在 for 循环中只前进一次?

Why does generator only advances once with for loop?

我很惊讶我在互联网上找不到类似的东西。

class Node:
    def __init__(self, value):
        self.value = value
        self.prev = None
        self.next = None

class DoublyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    # bunch of other methods here removed for clarity

    def removeNodesWithValue(self, value):
        for node in self._getAllNodes(value):
            print('removing')
            self._remove(node)

    # I haven't provided this method before I got the answer
    def _remove(self, node):
        ...
        node.prev = None
        node.next = None
        ...

    def _getAllNodes(self, value):
        cur = self.head
        while cur is not None:
            if cur.value == value:
                yield cur
            cur = cur.next

if __name__ == '__main__':
    l = DoublyLinkedList()
    l.insertAfter(l.head, Node(1))
    l.insertAfter(l.head, Node(1))
    l.insertAfter(l.head, Node(1))
    l.removeNodesWithValue(1)

此代码打印如下:

removing

但我希望它为每个找到的节点打印 3 次。 现在,如果我将这一行 for node in self._getAllNodes(value) 更改为这一行 for node in list(self._getAllNodes(value)),它只会按预期打印 3 次。

有人知道为什么 for 循环只从生成器中获取一个元素而不是全部 3 个吗?

完整代码:https://pastebin.com/QEasZSnK

更新:

根据 ShadowRanger 的回答,将 _getAllNodes 更改为此可以解决问题:

def _getAllNodes(self, value):
    cur = self.head
    while cur is not None:
        next = cur.next
        if cur.value == value:
            yield cur
        cur = next

您忽略了提供 remove 的定义,但心理调试表明它 None 超出了它正在删除的节点的 next 属性。问题是,您的生成器在 yield 处暂停,将节点返回给调用者,然后调用者 remove 发送它。当生成器恢复时,它仍然有对已删除节点的引用,并尝试从中获取 next 属性,但 next 现在是 None,所以它看起来像你'立即重新完成。

解决方案:

  1. 缓存 next before yielding
  2. 在移除
  3. 之前让removeAllNodes将所有节点缓存到list
  4. 跳过所有这些废话并将 self.head = self.tail = None 设置为 removeAllNodes 中的唯一代码,让垃圾收集器处理清理现在未引用的节点(这可能不是立即的,如果引用循环是涉及或您没有使用 CPython 参考解释器,但在清理之前有一个短暂的延迟通常是好的;如果需要,您可以使用 weakref 某种类型的代理进行反向链接以避免循环)