Removing Item From List - during iteration - what&

2019-01-01 09:36发布

As an experiment, I did this:

letters=['a','b','c','d','e','f','g','h','i','j','k','l']
for i in letters:
    letters.remove(i)
print letters

The last print shows that not all items were removed ? (every other was).

IDLE 2.6.2      
>>> ================================ RESTART ================================
>>> 
['b', 'd', 'f', 'h', 'j', 'l']
>>> 

What's the explanation for this ? How it could this be re-written to remove every item ?

8条回答
与君花间醉酒
2楼-- · 2019-01-01 09:50

You cannot iterate over a list and mutate it at the same time, instead iterate over a slice:

letters=['a','b','c','d','e','f','g','h','i','j','k','l']
for i in letters[:]: # note the [:] creates a slice
     letters.remove(i)
print letters

That said, for a simple operation such as this, you should simply use:

letters = []
查看更多
裙下三千臣
3楼-- · 2019-01-01 09:50
    #!/usr/bin/env python
    import random
    a=range(10)

    while len(a):
        print a
        for i in a[:]:
            if random.random() > 0.5:
                print "removing: %d" % i
                a.remove(i)
            else:
                print "keeping: %d"  % i           

    print "done!"
    a=range(10)

    while len(a):
        print a
        for i in a:
            if random.random() > 0.5:
                print "removing: %d" % i
                a.remove(i)
            else:
                print "keeping: %d"  % i           

    print "done!"

I think this explains the problem a little better, the top block of code works, whereas the bottom one doesnt.

Items that are "kept" in the bottom list never get printed out, because you are modifiying the list you are iterating over, which is a recipe for disaster.

查看更多
谁念西风独自凉
4楼-- · 2019-01-01 09:58

Probably python uses pointers and the removal starts at the front. The variable „letters“ in the second line partially has a different value than tha variable „letters“ in the third line. When i is 1 then a is being removed, when i is 2 then b had been moved to position 1 and c is being removed. You can try to use „while“.

查看更多
琉璃瓶的回忆
5楼-- · 2019-01-01 10:02

Some answers explain why this happens and some explain what you should've done. I'll shamelessly put the pieces together.


What's the reason for this?

Because the Python language is designed to handle this use case differently. The documentation makes it clear:

It is not safe to modify the sequence being iterated over in the loop (this can only happen for mutable sequence types, such as lists). If you need to modify the list you are iterating over (for example, to duplicate selected items) you must iterate over a copy.

Emphasis mine. See the linked page for more -- the documentation is copyrighted and all rights are reserved.

You could easily understand why you got what you got, but it's basically undefined behavior that can easily change with no warning from build to build. Just don't do it.

It's like wondering why i += i++ + ++i does whatever the hell it is it that line does on your architecture on your specific build of your compiler for your language -- including but not limited to trashing your computer and making demons fly out of your nose :)


How it could this be re-written to remove every item?

  • del letters[:] (if you need to change all references to this object)
  • letters[:] = [] (if you need to change all references to this object)
  • letters = [] (if you just want to work with a new object)

Maybe you just want to remove some items based on a condition? In that case, you should iterate over a copy of the list. The easiest way to make a copy is to make a slice containing the whole list with the [:] syntax, like so:

#remove unsafe commands
commands = ["ls", "cd", "rm -rf /"]
for cmd in commands[:]:
  if "rm " in cmd:
    commands.remove(cmd)

If your check is not particularly complicated, you can (and probably should) filter instead:

commands = [cmd for cmd in commands if not is_malicious(cmd)]
查看更多
忆尘夕之涩
6楼-- · 2019-01-01 10:02

You cannot modify the list you are iterating, otherwise you get this weird type of result. To do this, you must iterate over a copy of the list:

for i in letters[:]:
  letters.remove(i)
查看更多
何处买醉
7楼-- · 2019-01-01 10:03

what you want to do is:

letters[:] = []

or

del letters[:]

This will preserve original object letters was pointing to. Other options like, letters = [], would create a new object and point letters to it: old object would typically be garbage-collected after a while.

The reason not all values were removed is that you're changing list while iterating over it.

ETA: if you want to filter values from a list you could use list comprehensions like this:

>>> letters=['a','b','c','d','e','f','g','h','i','j','k','l']
>>> [l for l in letters if ord(l) % 2]
['a', 'c', 'e', 'g', 'i', 'k']
查看更多
登录 后发表回答