为什么生成器在 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
,所以它看起来像你'立即重新完成。
解决方案:
- 缓存
next
before yield
ing
- 在移除
之前让removeAllNodes
将所有节点缓存到list
- 跳过所有这些废话并将
self.head = self.tail = None
设置为 removeAllNodes
中的唯一代码,让垃圾收集器处理清理现在未引用的节点(这可能不是立即的,如果引用循环是涉及或您没有使用 CPython 参考解释器,但在清理之前有一个短暂的延迟通常是好的;如果需要,您可以使用 weakref
某种类型的代理进行反向链接以避免循环)
我很惊讶我在互联网上找不到类似的东西。
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
,所以它看起来像你'立即重新完成。
解决方案:
- 缓存
next
beforeyield
ing - 在移除 之前让
- 跳过所有这些废话并将
self.head = self.tail = None
设置为removeAllNodes
中的唯一代码,让垃圾收集器处理清理现在未引用的节点(这可能不是立即的,如果引用循环是涉及或您没有使用 CPython 参考解释器,但在清理之前有一个短暂的延迟通常是好的;如果需要,您可以使用weakref
某种类型的代理进行反向链接以避免循环)
removeAllNodes
将所有节点缓存到list