如何更改要迭代的容器?

时间:2019-10-12 23:03:05

标签: 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被跳过)。似乎列表迭代是在索引之上而不是元素之上,因此在迭代过程中只能安全地删除后续列表项(而不是先前列表项)。

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

  1. 确实会迭代新元素(与list.append一样)
  2. 被删除的元素随后不会 被迭代,
  3. 是否要迭代(或不迭代)一个元素永远不会受到添加/删除 other 元素的影响。

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

2 个答案:

答案 0 :(得分:3)

您可以通过使用list方法的适当实现子类化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] 的行为,该方法将在删除的索引小于当前迭代器索引时减少迭代器指向的索引:

container = List((1, 2, 3, 4))
for i in container:
    if i == 2:
        container.remove(1)
    for j in container:
        print(i, j)

这样:

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

输出:

struct Info {
    let author, title: String
    let tags: [Tags]
    let price: [Double]
    enum Tags: String, Codable {
        case nonfiction, biography, fiction
    }
}

答案 1 :(得分:1)

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

我建议您实际上没有从列表中删除任何值。而是将它们添加到另一个容器中,也许是set(如果它们是可哈希的)。这假定值是唯一的。但是,如果不是这样,您可能会遇到用任何方法将它们从列表中删除的问题。

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

正如评论所建议的那样,该循环将打印出1248