#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']