Добавление и удаление экземпляра в список изнутри

#python #python-3.x #list #object

#python #python-3.x #Список #объект

Вопрос:

Следующий код определяет класс (Wall), который при создании экземпляра объекта добавляется в список (in_progress), и как только его атрибут (progress) достигает 3, он удаляется из этого списка и перемещается в другой (встроенный).

 in_progress = []
built = []

class Wall:
    global in_progress, built

    def __init__(self):
        self.progress = 0
        in_progress.append(self)

    def build(self):
        self.progress  = 1

        if self.progress == 3:
            in_progress.remove(self)
            built.append(self)
  

Это удобно, поскольку независимо от того, сколько стен в списке «in_progress», я могу запустить:

 for wall in in_progress:
    wall.build()
  

и в конечном итоге «in_progress» будет пустым. Однако я провел несколько тестов, и что-то странное происходит, когда экземпляр в in_progress достигает progress = 3.

Например. Давайте создадим экземпляр трех стен:

 Wall()
Wall()
Wall() 

#check in_progress
in_progress
--->
[<__main__.Wall at 0x7f4b84e68cf8>,
 <__main__.Wall at 0x7f4b84e68c50>,
 <__main__.Wall at 0x7f4b84e68f28>]

#check attribute progress

for wall in in_progress:
    print(f'{wall}: {wall.progress}')
--->
<__main__.Wall object at 0x7f4b84e68cf8>: 0
<__main__.Wall object at 0x7f4b84e68c50>: 0
<__main__.Wall object at 0x7f4b84e68f28>: 0

#'build' on them 2 times
for wall in in_progress:
    wall.build()

for wall in in_progress:
    print(f'{wall}: {wall.progress}')
--->
<__main__.Wall object at 0x7f4b84e68cf8>: 2
<__main__.Wall object at 0x7f4b84e68c50>: 2
<__main__.Wall object at 0x7f4b84e68f28>: 2
  

Если мы запустим последний код еще раз, мы ожидаем найти список in_progress пустым, но то, что мы находим, это:

 #'build' on them once more
for wall in in_progress:
    wall.build()

for wall in in_progress:
    print(f'{wall}: {wall.progress}')
--->
<__main__.Wall object at 0x7f4b84e68c50>: 2
  

Если мы проверим созданный список, мы обнаружим, что осталось 2 стены, но должно быть 3.
Почему это происходит?

Комментарии:

1. Вы никогда не добавляли его self в in_progress список, почему вы могли бы его удалить?

2. Вы правы, извините. Я это исправил.

3. Не могли бы вы дополнить свой код комментариями? Это трудно читать. Спасибо!!!

4. Для целей моей программы проще добавить стены в in_progress внутри init , но у меня все еще есть другая проблема, связанная с этим кодом. Должен ли я изменить post? вопрос? В любом случае спасибо

5. Продолжайте и отредактируйте вопрос

Ответ №1:

Проблема в вашей функции сборки заключается в том, что вы пытаетесь изменить тот же список, по которому выполняете итерацию, что вызывает эту странную проблему, попробуйте выполнить следующее, и вы не должны увидеть проблему. Я копирую список в другую переменную через copy.copyhttps://docs.python.org/3/library/copy.html

 import copy
in_progress = []
built = []

class Wall:
    global in_progress, built

    def __init__(self):
        self.progress = 0
        in_progress.append(self)

    def build(self):
        global in_progress
        self.progress  = 1
        #Make a copy of the list and operate on that
        copy_in_progress = copy.copy(in_progress)
        if self.progress == 3:
            copy_in_progress.remove(self)
            built.append(self)
        in_progress = copy_in_progress

Wall()
Wall()
Wall()

print(in_progress)
#[<__main__.Wall object at 0x108259908>, 
#<__main__.Wall object at 0x108259940>, 
#<__main__.Wall object at 0x1082599e8>]

for wall in in_progress:
    print(f'{wall}: {wall.progress}')

#<__main__.Wall object at 0x108259908>: 0
#<__main__.Wall object at 0x108259940>: 0
#<__main__.Wall object at 0x1082599e8>: 0

for wall in in_progress:
    wall.build()
    wall.build()

for wall in in_progress:
    print(f'{wall}: {wall.progress}')


#<__main__.Wall object at 0x108259908>: 2
#<__main__.Wall object at 0x108259940>: 2
#<__main__.Wall object at 0x1082599e8>: 2
for wall in in_progress:
    wall.build()

for wall in in_progress:
    print(f'{wall}: {wall.progress}')
#Nothing is printed
  

Ответ №2:

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

 >>> q = ['a', 'ab', 'abc', 'again', 'b', 'a1', 'c', 'a2', 'ack']
>>> for pos in q:
...     if pos.startswith('a'):
...             q.remove(pos)
... 
>>> q
['ab', 'again', 'b', 'c', 'ack']
  

Здесь, когда удаляется первый элемент, список сдвигается вниз, так что первый элемент становится ‘ab’. Затем в верхней части цикла «следующим» элементом является ‘abc’, поскольку он теперь находится на второй позиции, поэтому ‘ab’ никогда не проверяется на удаление. Аналогично, ‘again’ и ‘ack’ не удаляются, потому что они никогда не тестировались. На самом деле, ‘b’ и ‘c’ остаются в списке не потому, что они не начинаются с ‘a’, но они также никогда не тестировались, поскольку список сместился, и цикл пропустил их тоже!

Если вы выполните итерацию по копии или фрагменту вашего исходного списка, это, вероятно, даст вам то, что вам нужно, но будьте осторожны с любым сценарием, в котором вы выполняете итерацию по чему-то, что обновляется одновременно.

 >>> q = ['a', 'ab', 'abc', 'again', 'b', 'a1', 'c', 'a2', 'ack']
>>> for pos in q[:]:
...     if pos.startswith('a'):
...             q.remove(pos)
... 
>>> q
['b', 'c']