Добавление и удаление экземпляра в список изнутри - PullRequest
4 голосов
/ 04 апреля 2019

Следующий код определяет класс (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 достигает прогресса = 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. Почему это происходит?

Ответы [ 2 ]

1 голос
/ 04 апреля 2019

Обход списка и изменение его во время обхода может привести к некоторому нелогичному поведению, такому как это. Делая 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» никогда не проверяется на удаление. Точно так же «снова» и «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']
1 голос
/ 04 апреля 2019

Проблема в вашей функции сборки заключается в том, что вы пытаетесь изменить тот же список, по которому вы выполняете итерацию, что приводит к возникновению этой странной проблемы, попробуйте выполнить следующее, и вы не должны видеть проблему.Я копирую список в другую переменную с помощью copy.copy https://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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...