Как изменить контейнер, который повторяется? - PullRequest
0 голосов
/ 13 октября 2019

Какие контейнеры должным образом поддерживают мутацию во время итерации?

Например:

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. Удаленные элементы не впоследствии get iterated,
  3. Независимо от того, будет ли элемент повторяться (или нет), никогда не будет зависеть от добавления / удаления других элементов.

У меня есть приложениеИмеется в виду реестр обратных вызовов событий. При срабатывании я бы хотел, чтобы обратные вызовы имели возможность охотно регистрировать или отменять регистрацию других обратных вызовов для того же события. (Если, например, я перебрал временную копию контейнера, мне нужно было бы подождать, пока событие не будет запущено во второй раз, прежде чем изменения вступят в силу.)

Ответы [ 2 ]

3 голосов
/ 13 октября 2019

Вы можете настроить поведение 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
1 голос
/ 13 октября 2019

Поведение, о котором вы спрашиваете, является подробностью реализации задействованного итератора. Как вы заметили, тип 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

Как показывают комментарии, этот цикл с распечаткой 1, 2, 4 и 8.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...