在 Python 中迭代列表时从列表中删除项目时出现奇怪的结果

发布于 2024-11-14 08:01:06 字数 416 浏览 4 评论 0原文

我有这段代码:

numbers = list(range(1, 50))

for i in numbers:
    if i < 20:
        numbers.remove(i)

print(numbers)

但是,我得到的结果是:
<代码>[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]

当然,我希望 20 以下的数字不会出现在结果中。看起来我在删除时做错了什么。

I've got this piece of code:

numbers = list(range(1, 50))

for i in numbers:
    if i < 20:
        numbers.remove(i)

print(numbers)

But, the result I'm getting is:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]

Of course, I'm expecting the numbers below 20 to not appear in the results. Looks like I'm doing something wrong with the remove.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

随波逐流 2024-11-21 08:01:06

您在迭代列表时正在修改列表。这意味着第一次循环时,i == 1,因此 1 将从列表中删除。然后 for 循环转到列表中的第二项,它不是 2,而是 3!然后将其从列表中删除,然后 for 循环继续处理列表中的第三项,即现在的 5。依此类推。也许像这样更容易可视化,用 ^ 指向 i 的值:

[1, 2, 3, 4, 5, 6...]
 ^

这是列表最初的状态;然后 1 被删除,循环转到列表中的第二项:

[2, 3, 4, 5, 6...]
    ^
[2, 4, 5, 6...]
       ^

依此类推。

没有好的方法可以在迭代列表时改变列表的长度。你能做的最好的事情是这样的:

numbers = [n for n in numbers if n >= 20]

或者这样,对于就地更改(括号中的东西是一个生成器表达式,它在切片分配之前隐式转换为元组):

numbers[:] = (n for n in numbers if n >= 20)

如果你想对 < 执行操作code>n 在删除它之前,您可以尝试的一个技巧是:

for i, n in enumerate(numbers):
    if n < 20:
        print("do something")
        numbers[i] = None
numbers = [n for n in numbers if n is not None]

You're modifying the list while you iterate over it. That means that the first time through the loop, i == 1, so 1 is removed from the list. Then the for loop goes to the second item in the list, which is not 2, but 3! Then that's removed from the list, and then the for loop goes on to the third item in the list, which is now 5. And so on. Perhaps it's easier to visualize like so, with a ^ pointing to the value of i:

[1, 2, 3, 4, 5, 6...]
 ^

That's the state of the list initially; then 1 is removed and the loop goes to the second item in the list:

[2, 3, 4, 5, 6...]
    ^
[2, 4, 5, 6...]
       ^

And so on.

There's no good way to alter a list's length while iterating over it. The best you can do is something like this:

numbers = [n for n in numbers if n >= 20]

or this, for in-place alteration (the thing in parens is a generator expression, which is implicitly converted into a tuple before slice-assignment):

numbers[:] = (n for n in numbers if n >= 20)

If you want to perform an operation on n before removing it, one trick you could try is this:

for i, n in enumerate(numbers):
    if n < 20:
        print("do something")
        numbers[i] = None
numbers = [n for n in numbers if n is not None]
平定天下 2024-11-21 08:01:06

从列表末尾开始向后走:

li = list(range(1, 15))
print(li)

for i in range(len(li) - 1, -1, -1):
    if li[i] < 6:
        del li[i]
        
print(li)

结果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] 
[6, 7, 8, 9, 10, 11, 12, 13, 14]

Begin at the list's end and go backwards:

li = list(range(1, 15))
print(li)

for i in range(len(li) - 1, -1, -1):
    if li[i] < 6:
        del li[i]
        
print(li)

Result:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] 
[6, 7, 8, 9, 10, 11, 12, 13, 14]
懵少女 2024-11-21 08:01:06

@senderle的答案是走得更远!

话虽如此,为了进一步说明你的问题,如果你仔细想想,你总是想删除索引 0 二十次:

[1,2,3,4,5............50]
 ^
[2,3,4,5............50]
 ^
[3,4,5............50]
 ^

所以你可以实际上是这样的:

aList = list(range(50))
i = 0
while i < 20:
    aList.pop(0)
    i += 1

print(aList) #[21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]

我希望它有帮助。


据我所知,下面的这些都是不错的做法。

编辑(更多):

lis = range(50)
lis = lis[20:]

也会完成这项工作。

编辑2(我很无聊):

functional = filter(lambda x: x> 20, range(50))

@senderle's answer is the way to go!

Having said that to further illustrate even a bit more your problem, if you think about it, you will always want to remove the index 0 twenty times:

[1,2,3,4,5............50]
 ^
[2,3,4,5............50]
 ^
[3,4,5............50]
 ^

So you could actually go with something like this:

aList = list(range(50))
i = 0
while i < 20:
    aList.pop(0)
    i += 1

print(aList) #[21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]

I hope it helps.


The ones below are not bad practices AFAIK.

EDIT (Some more):

lis = range(50)
lis = lis[20:]

Will do the job also.

EDIT2 (I'm bored):

functional = filter(lambda x: x> 20, range(50))
明明#如月 2024-11-21 08:01:06

所以我找到了一个解决方案,但它真的很笨拙......

首先,你创建一个索引数组,在其中列出你想要删除的所有索引',如下所示

numbers = range(1, 50)
index_arr = []

for i in range(len(numbers):
    if numbers[i] < 20:
        index_arr.append(i)

,然后你想从数字列表中删除所有条目索引保存在index_arr中。您将遇到的问题与以前相同。因此,在从数字 arr 中删除一个数字后,您必须从 index_arr 中的每个索引中减去 1,如下所示:

numbers = range(1, 50)
index_arr = []

for i in range(len(numbers):
    if numbers[i] < 20:
        index_arr.append(i)

for del_index in index_list:
    numbers.pop(del_index)

    #the nasty part
    for i in range(len(index_list)):
        index_list[i] -= 1

它会起作用,但我想这不是预期的方法

So I found a solution but it's really clumsy...

First of all you make an index array, where you list all the index' you want to delete like in the following

numbers = range(1, 50)
index_arr = []

for i in range(len(numbers):
    if numbers[i] < 20:
        index_arr.append(i)

after that you want to delete all the entries from the numbers list with the index saved in the index_arr. The problem you will encounter is the same as before. Therefore you have to subtract 1 from every index in the index_arr after you just removed a number from the numbers arr, like in the following:

numbers = range(1, 50)
index_arr = []

for i in range(len(numbers):
    if numbers[i] < 20:
        index_arr.append(i)

for del_index in index_list:
    numbers.pop(del_index)

    #the nasty part
    for i in range(len(index_list)):
        index_list[i] -= 1

It will work, but I guess it's not the intended way to do it

寂寞陪衬 2024-11-21 08:01:06

作为@Senderle答案的附加信息,仅用于记录,我认为当python在“序列类型”上看到for时,可视化场景背后的逻辑很有帮助。

假设我们有:

lst = [1, 2, 3, 4, 5]

for i in lst:
    print(i ** 2)

它实际上会是:

index = 0
while True:
    try:
        i = lst.__getitem__(index)
    except IndexError:
        break
    print(i ** 2)
    index += 1

就是这样,当我们在序列类型或可迭代对象上使用 for 时,它有一个 try-catch 机制(虽然有点不同) - 调用 next()StopIteration 异常)。

*我想说的是,python 将跟踪一个名为 index 的独立变量,因此无论列表发生什么情况(删除或添加),python 都会递增该变量并调用 < code>__getitem__() 方法使用“此变量”并请求项目。

As an additional information to @Senderle's answer, just for records, I thought it's helpful to visualize the logic behind the scene when python sees for on a "Sequence type".

Let's say we have :

lst = [1, 2, 3, 4, 5]

for i in lst:
    print(i ** 2)

It is actually going to be :

index = 0
while True:
    try:
        i = lst.__getitem__(index)
    except IndexError:
        break
    print(i ** 2)
    index += 1

That's what it is, there is a try-catch mechanism that for has when we use it on a Sequence types or Iterables(It's a little different though - calling next() and StopIteration Exception).

*All I'm trying to say is, python will keep track of an independent variable here called index, so no matter what happens to the list (removing or adding), python increments that variable and calls __getitem__() method with "this variable" and asks for item.

那一片橙海, 2024-11-21 08:01:06

以 @eyquem 的答案为基础并进行简化...

问题是,当您迭代时,元素会从您的下方被拉出,当您前进到下一个数字时会跳过数字。

如果从末尾开始向后移动,则即时删除项目并不重要,因为当它进入“下一个”项目(实际上是前一个项目)时,删除不会影响列表的前半部分。

只需将 reversed() 添加到迭代器即可解决问题。注释是一种很好的形式,可以防止未来的开发人员“整理”您的代码并神秘地破坏它。

for i in reversed(numbers): # `reversed` so removing doesn't foobar iteration
  if i < 20:
    numbers.remove(i)

Building on and simplying the answer by @eyquem ...

The problem is that elements are being yanked out from under you as you iterate, skipping numbers as you progress to what was the next number.

If you start from the end and go backwards, removing items on-the-go won't matter, because when it steps to the "next" item (actually the prior item), the deletion does not affect the first half of the list.

Simply adding reversed() to your iterator solves the problem. A comment would be good form to preclude future developers from "tidying up" your code and breaking it mysteriously.

for i in reversed(numbers): # `reversed` so removing doesn't foobar iteration
  if i < 20:
    numbers.remove(i)
Oo萌小芽oO 2024-11-21 08:01:06

为迭代创建数字的浅拷贝,将确保用于迭代的列表不被修改。浅拷贝可以使用list()copy.copy()进行,并且将确保要删除的元素的标识相同,并减少临时列表的大小。代码:

...
for i in list(numbers):
    if i < 20:
        numbers.remove(i)
...

Making a shallow copy of numbers for iteration, will ensure that the list use for iteration is not modified. The shallow copy can be made using list(), or copy.copy(), and will ensure that the identify of the elements to remove is the same, and reduce the size of the temporary list. Code:

...
for i in list(numbers):
    if i < 20:
        numbers.remove(i)
...
独﹏钓一江月 2024-11-21 08:01:06

您还可以使用 continue 来忽略小于 20 的值

mylist = []

for i in range(51):
    if i<20:
        continue
    else:
        mylist.append(i)
print(mylist)

You could also use continue to ignore the values less than 20

mylist = []

for i in range(51):
    if i<20:
        continue
    else:
        mylist.append(i)
print(mylist)
慢慢从新开始 2024-11-21 08:01:06

Python 3.3 开始,您可以使用列表 copy() 方法作为迭代器:

numbers = list(range(1, 50))

for i in numbers.copy():
    if i < 20:
        numbers.remove(i)
print(numbers)

[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]

Since Python 3.3 you may use the list copy() method as the iterator:

numbers = list(range(1, 50))

for i in numbers.copy():
    if i < 20:
        numbers.remove(i)
print(numbers)

[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
自控 2024-11-21 08:01:06

您可以使用 list() 来表示数字< /code> 创建不同的复制数字,如下所示:

numbers = list(range(1, 50))
       # ↓ ↓ Here ↓ ↓
for i in list(numbers):
    if i < 20:
        numbers.remove(i)

print(numbers) # [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 
               #  31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 
               #  f42, 43, 44, 45, 46, 47, 48, 49]

You can use list() for numbers to create a different copied numbers as shown below:

numbers = list(range(1, 50))
       # ↓ ↓ Here ↓ ↓
for i in list(numbers):
    if i < 20:
        numbers.remove(i)

print(numbers) # [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 
               #  31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 
               #  f42, 43, 44, 45, 46, 47, 48, 49]
如梦初醒的夏天 2024-11-21 08:01:06

嗯,我需要这样的东西。
答案是使用 while 语句。
这对我的情况确实有帮助。所以我想将其作为答案发布。

numbers = list(range(1, 50))
while numbers:
    current_number = numbers[0]
    if current_number < 20:
        numbers.remove(current_number)
    else:
        break
numbers
>>> [20, 21, 22, 23, 24, 25, ..., 49]

我还想使用 whilefor 使用 [:] 添加特殊情况,但它可能超出了范围,所以我'会保留它。

Well, I needed a sort of this.
The answer was to use while statement.
It really helped me in my case. So I'd like to post it as an answer.

numbers = list(range(1, 50))
while numbers:
    current_number = numbers[0]
    if current_number < 20:
        numbers.remove(current_number)
    else:
        break
numbers
>>> [20, 21, 22, 23, 24, 25, ..., 49]

I also wanted to add my special case using while and for using [:], but it might be out of the scope, so I'll keep it.

如此安好 2024-11-21 08:01:06

有点晚了,但我想添加我的答案。我发现了以下相当简单的技巧:

for x in list[::-1]:
    if validate(x):
        list.pop(x)

这样,您可以从末尾开始迭代。当您删除第 n 个元素时,元素 n+1,n+2... 会将其索引减一,但由于您要向后移动,因此不会完好无损地后面(或在本例中是前面的)元素。 。

A bit late but I feel like adding my answer. I found the following trick which is fairly simply

for x in list[::-1]:
    if validate(x):
        list.pop(x)

This way, you iterate starting from the end. When you remove the n-th element, the elements n+1,n+2... decrease their index by one, but because you are going backwards this won't intact the following (or preceding, in this case) elements..

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文