从内部添加和删除实例列表

时间:2019-04-04 01:05:26

标签: python-3.x list object

以下代码定义了一个类(Wall),当实例化该对象时,该对象将添加到列表(in_progress),并且一旦属性(progress)达到3,它将从该列表中删除并移至另一个列表(build)。

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堵墙。 为什么会这样?

2 个答案:

答案 0 :(得分:1)

构建函数中的问题是您试图修改要迭代的同一列表,这导致发生此奇怪的问题,请尝试以下操作,并且不应看到该问题。我正在通过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

答案 1 :(得分:1)

遍历列表并在遍历期间对其进行更改可能会导致一些违反直觉的行为,例如这样。通过对当前所处元素的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']