Python 如何变异正在迭代的容器?

Python 如何变异正在迭代的容器?,python,iteration,mutable,Python,Iteration,Mutable,在python中,哪些容器正确地支持迭代期间的变异 例如: container = [1, 2, 3, 4] for i in container: print(i) if i == 2: container.append(8) 输出1 2 3 4 8(可以在迭代过程中追加列表) 但是,如果我将.append(8)替换为.remove(1),则输出将变为1 2 4(即跳过元素3)。似乎列表迭代是在索引而不是元素上进行的,因此在迭代期间只能安全地删除后续列表项(而

在python中,哪些容器正确地支持迭代期间的变异

例如:

container = [1, 2, 3, 4]
for i in container:
    print(i)
    if i == 2:
        container.append(8)
输出
1 2 3 4 8
(可以在迭代过程中追加列表)

但是,如果我将
.append(8)
替换为
.remove(1)
,则输出将变为
1 2 4
(即跳过元素
3
)。似乎列表迭代是在索引而不是元素上进行的,因此在迭代期间只能安全地删除后续列表项(而不是以前的列表项)

标准库中是否有允许在迭代期间添加和删除元素的容器,其行为如下:

  • 新元素确实会被迭代(对于
    list.append
  • 删除的元素随后不会被迭代
  • 元素是否被迭代(或不被迭代)永远不会受到其他元素的添加/删除的影响

  • 我想到的应用程序是事件回调的注册表。触发时,我希望回调能够急切地注册或取消注册同一事件的其他回调。(例如,如果我迭代了容器的临时副本,我需要等待事件再次触发,然后更改才会开始生效。)

    您询问的行为是所涉及的迭代器的实现细节。正如您所注意到的,
    list\u迭代器
    类型使用内部索引,因此删除已访问的元素会导致问题,因为它会更改列表中所有后续值的索引

    我的建议是,实际上不要从列表中删除任何值。相反,将它们添加到另一个容器中,可能是一个
    集合
    (如果它们是可散列的)。这假定值是唯一的。但是,如果它们不是,您可能无法通过任何方法将它们从列表中删除

    container = [1, 2, 3, 4]
    removed = set()
    for i in container:
        if i not in removed:         # skip values that have been "removed"
            print(i)
            if i == 2:
                removed.add(1)       # since we've already visited 1, this has no real effect
                removed.add(3)       # this does work though, we won't print the 3
                container.append(8)  # additions of new elements work as normal
    

    正如注释所示,带有打印输出
    1
    2
    4
    8
    的循环是所涉及迭代器的实现细节。正如您所注意到的,
    list\u迭代器
    类型使用内部索引,因此删除已访问的元素会导致问题,因为它会更改列表中所有后续值的索引

    我的建议是,实际上不要从列表中删除任何值。相反,将它们添加到另一个容器中,可能是一个
    集合
    (如果它们是可散列的)。这假定值是唯一的。但是,如果它们不是,您可能无法通过任何方法将它们从列表中删除

    container = [1, 2, 3, 4]
    removed = set()
    for i in container:
        if i not in removed:         # skip values that have been "removed"
            print(i)
            if i == 2:
                removed.add(1)       # since we've already visited 1, this has no real effect
                removed.add(3)       # this does work though, we won't print the 3
                container.append(8)  # additions of new elements work as normal
    

    如注释所示,该循环使用打印输出
    1
    2
    4
    ,和
    8

    您可以自定义
    列表的行为,方法是使用
    remove
    方法的适当实现对其进行子类化,该方法在要删除的索引小于当前迭代器索引时减少迭代器指向的索引:

    from weakref import WeakSet
    
    class IterList:
        def __init__(self, lst):
            self.list = lst
            self.index = 0
    
        def __next__(self):
            if self.index == len(self.list):
                raise StopIteration
            value = self.list[self.index]
            self.index += 1
            return value
    
    class List(list):
        iterators = WeakSet()
    
        def __iter__(self):
            iterator = IterList(self)
            self.iterators.add(iterator)
            return iterator
    
        def remove(self, item):
            index = super().index(item)
            for iterator in self.iterators:
                if index < iterator.index:
                    iterator.index -= 1
            del self[index]
    
    产出:

    1 1
    1 2
    1 3
    1 4
    2 2
    2 3
    2 4
    3 2
    3 3
    3 4
    4 2
    4 3
    4 4
    

    通过使用
    remove
    方法的适当实现对其子类化,可以自定义
    list
    的行为,该方法在要删除的索引小于当前迭代器索引时减少迭代器指向的索引:

    from weakref import WeakSet
    
    class IterList:
        def __init__(self, lst):
            self.list = lst
            self.index = 0
    
        def __next__(self):
            if self.index == len(self.list):
                raise StopIteration
            value = self.list[self.index]
            self.index += 1
            return value
    
    class List(list):
        iterators = WeakSet()
    
        def __iter__(self):
            iterator = IterList(self)
            self.iterators.add(iterator)
            return iterator
    
        def remove(self, item):
            index = super().index(item)
            for iterator in self.iterators:
                if index < iterator.index:
                    iterator.index -= 1
            del self[index]
    
    产出:

    1 1
    1 2
    1 3
    1 4
    2 2
    2 3
    2 4
    3 2
    3 3
    3 4
    4 2
    4 3
    4 4
    

    你可以尝试所有的内置容器。回答自己的问题是允许的-Set和deque都会引发异常。您可以使用所有内置容器进行实验。允许回答您自己的问题-Set和deque都会引发异常。通过将列表转换为自己的迭代器,您不可能同时对列表进行多次迭代:
    for i in container:for j in container:print(i,j)
    将永远运行,因为外部循环总是在内部循环重置的同时重置。我一直在等待有人指出这一点,因为当OP似乎没有在同一列表上使用多个迭代器时,我犹豫是否要使解决方案比需要的更复杂。但现在有人指出了这一点,我继续并相应地更新了答案。:-)看起来不错!我唯一的进一步建议(同样是以可能不必要的复杂性为代价)是使用
    weakref.WeakSet
    ,这样您就不会永远保持对所有迭代器的引用。好建议。尽管行
    self.list.iterators.remove(self)
    正好可以做到这一点,但使用
    weakref.WeakSet
    将使显式引用删除变得不必要。按建议更新。谢谢是的,依赖迭代器从列表中删除自身的危险在于,如果迭代器没有运行到完成,它将无法工作。例如,如果有一个
    break
    语句提前停止了
    For
    循环。通过将列表转换为自己的迭代器,您不可能同时对列表进行多次迭代:
    For i in container:For j in container:print(i,j)
    将永远运行,因为外部循环总是在内部循环重置的同时重置。我一直在等待有人指出这一点,因为当OP似乎没有在同一列表上使用多个迭代器时,我犹豫是否要使解决方案比需要的更复杂。但现在有人指出了这一点,我继续并相应地更新了答案。:-)看起来不错!我唯一的进一步建议(同样是以可能不必要的复杂性为代价)是使用
    weakref.WeakSet
    ,这样您就不会永远保持对所有迭代器的引用。好建议。尽管行
    self.list.iterators.remove(self)
    正好可以做到这一点,但使用
    weakref.WeakSet
    将使显式引用删除变得不必要。按建议更新。谢谢是的,依赖迭代器删除其