简体   繁体   English

从内部追加和删除实例到列表

[英]Appending and removing instance to list from inside

The following code defines a class (Wall) which when it's instantiated the object is added to a list (in_progress) and once it's attribute (progress) reaches 3 it is removed from that list and moved to another one (built). 下面的代码定义了一个类(Wall),当它被实例化时,对象被添加到列表(in_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)

This is convenient since no matter how many Walls there are in the list "in_progress" I can run: 这很方便,因为无论在“in_progress”列表中有多少Wall,我都可以运行:

for wall in in_progress:
    wall.build()

and eventually "in_progress" will be empty. 最终“in_progress”将是空的。 However I've done some tests and something strange happens when an instance in in_progress reaches progress = 3. 但是我做了一些测试,当in_progress中的实例达到progress = 3时会发生奇怪的事情。

For example. 例如。 Let's instantiate three walls: 让我们实例化三面墙:

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

If we run the last code one more time we expect to find the list in_progress empty but what we find is this: 如果我们再次运行最后一个代码,我们希望找到列表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

If we check the list built we find the 2 walls left but there should be 3. Why does this happen? 如果我们检查建立的列表,我们发现剩下2个墙但应该有3.为什么会发生这种情况?

The problem in your build function is that you are trying to modify the same list you are iterating on, which is causing this weird issue to happen, try as follows and you should not see the issue. 你的构建函数中的问题是你试图修改你正在迭代的相同列表,这导致这个奇怪的问题发生,尝试如下,你不应该看到问题。 I am copying the list to another variable via copy.copy https://docs.python.org/3/library/copy.html 我通过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

Traversing a list and altering it during traversal can cause some counterintuitive behavior, such as this. 遍历列表并在遍历期间更改它可能会导致一些违反直觉的行为,例如这样。 By doing the remove() of the element you are currently positioned at, the list is altered so that the next time through the loop, the next element is one beyond where you think you should be, since the list got shifted back one by the operation of remove() . 通过执行当前所在元素的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']

Here, when the first element is removed, the list shifts down so the first element becomes 'ab'. 这里,当第一个元素被移除时,列表向下移动,因此第一个元素变为'ab'。 Then at the top of the loop, the "next" element is 'abc', since it is now in the second position, so 'ab' never is tested for removal. 然后在循环的顶部,“下一个”元素是'abc',因为它现在处于第二个位置,所以'ab'永远不会被测试去除。 Similarly, 'again' and 'ack' don't get removed because they were never tested. 同样,“再次”和“确认”不会被删除,因为它们从未被测试过。 In fact, 'b' and 'c' remain in the list not because they do not start with 'a', but they also were never tested, since the list shifted and the loop skipped them over too! 实际上,'b'和'c'仍然在列表中,不是因为它们不是以'a'开头,而是它们也从未经过测试,因为列表移位了,循环也跳过它们!

If you iterate over a copy or a slice of your original list, that will probably get you what you need, but be careful about any scenario where you are iterating through something that is being updated at the same time. 如果迭代原始列表的副本或片段,那么可能会得到您所需要的内容,但请注意您正在迭代同时更新的内容的任何场景。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM