列表理解表达式错误中的列表 remove() 方法

List remove() method in list comprehension expression error

我正在创建一个 classical "set" class 来练习,我要做的第一件事是删除所有重复项。我知道我可以用字典键轻松做到这一点,但我想尝试提高我的列表理解力。这两个函数应该做同样的事情,但第二个不起作用。为什么?

for element in elements:
            if elements.count(element) > 1:
                elements.remove(element)
        print(elements)

第二个:

self.elements = [elements.remove(element) for element in elements
                 if elements.count(element) > 1]

不要遍历并从同一个列表中删除,如果您的对象是可散列的,您还应该使用 Counter 字典来计算每个元素的出现次数:

from collections import Counter
cn = Counter(elements)
# elements[:] changes original list
elements[:] = (ele for ele in elements if ch[ele] < 2)

在您的第二个代码中,因为 list.remove 是一个 inplace 操作,它会随时将 None's 添加到您的列表中 if elements.count(element) > 1True 否则什么也不做,所以这两个代码示例完全不同。

第一个代码如果确实有效,那只是偶然。当您从列表中删除一个元素时,指针之前指向的内容可能会发生变化,因此您最终会从列表中删除错误的元素。

你的第二个代码在做什么以及为什么你的第一个是错误方法的例子:

In [20]: l = [2,3,1,4,1,5]

In [21]: l = [l.remove(i)  if i > 1 else i for i in l]

In [22]: l
Out[22]: [None, 1, None, None]

因为您更改了指针值,所以您最终删除了第二个 1 并添加了一些 None,因为就像所有就地操作或未指定 return python 中的值默认为 return None。

如果您真的想获得一组唯一的所有元素,而不只是保留您的代码似乎正在尝试的唯一元素并保持顺序,collections.OrderedDict dict 将执行您的操作需要:

from collections import OrderedDict
elements[:] =  collections.OrderedDict.fromkeys(elements)

您的代码有两个问题。第一个问题是您明确询问的内容:列表理解版本将把一大堆 None 值分配给 self.elementsNone 值只是您调用 list.remove 的 return 值。它就地修改了列表,并且对 return 没有任何用处(所以它 returns None)。

理解 [element for element in elements if elements.count(element) == 1 or elements.remove(element)] 将与您的其他代码一样工作(因为 None 是错误的并且 or 短路),但它仍然会遇到第二个问题。 (这也是一个有点丑陋的 hack:由理解创建的新列表将具有与 elements 相同的内容,因为 remove 就地修改了 elements,这很令人困惑。写得很辛苦理解代码通常不是一个好主意。)

第二个问题是,在遍历列表时修改列表可能会导致问题。列表迭代器按索引工作。迭代器生成的第一项来自索引 0,第二项来自索引 1,依此类推。如果通过删除列表前面的项目来修改列表,则会移动所有后面项目的索引。

因此,假设您在迭代器向您显示第一项后删除了第一项(从索引 0 开始)。该列表会将所有后面的值向上移动,但迭代器不会知道这一点。它仍然会在下一个索引 1 处生成项目,即使它曾经是索引 2 处的项目(在修改列表之前)。最初位于索引 1 的项目(以及删除前一个项目后位于索引 0 的项目)将被迭代跳过。

这是此问题的一个简单示例,其中不会打印值 2、5 和 8:

L = list(range(10)) # [0,1,2,3,4,5,6,7,8,9]
for x in L:
    print(x)
    if x % 3 == 1: # true for 1,4, and 7
        L.remove(x)

在示例中,删除值的逻辑非常简单,我们绝不会跳过通常要删除的值(因此 L 最后的预期值为 [0,2,3,5,6,8,9]) ,其他代码可能效果不佳。

避免此问题的一种方法是迭代列表的副本,同时修改原始列表。在这种情况下,我们还需要 count 原件,而不是副本:

for element in elements[:]: # copy list with a slice here!
    if elements.count(element) > 1:
        elements.remove(element)  # modify the original list

虽然这相当低效,因为从列表中删除一个项目(在末尾以外的位置)需要花时间将所有后面的值向上移动一个位置。计数也很慢,因为您需要为每个项目迭代整个列表。跟踪您到目前为止看到的独特项目并在以后看到重复项目时跳过它们会更有效率:

seen = set()
results = []
for element in elements:
   if element not in seen:
       seen.add(element)
       results.append(element)

你甚至可以构建一个有点笨拙的列表理解(有副作用)这段代码:

seen = set()
results = [element for element in elements
           if not (element in seen or seen.add(element))]

更好的方法通常是将重复数据删除逻辑捆绑到生成器函数中(如 itertools 文档中的 unique_everseen recipe),然后用 list(dedupe(elements)) 调用它。