【问题标题】:Deleting elements of a python list during iteration在迭代期间删除python列表的元素
【发布时间】:2013-01-29 11:49:50
【问题描述】:

我在每个元素上都有一个 非常 大的列表,我必须对其进行许多操作。本质上,列表的每个元素都以各种方式附加,然后用于生成对象。然后使用这些对象生成另一个列表。

不幸的是,以幼稚的方式执行此操作会占用所有可用内存。

因此我想做以下事情:

for a in b:
    # Do many things with a
    c.append(C(modified_a))
    b[b.index(a)] = None # < Herein lies the rub

这似乎违反了在迭代期间不应修改列表的想法。有没有更好的方法来进行这种手动垃圾收集?

【问题讨论】:

  • 请在 cmets 中解释降级,否则无助于我以后提出更好的问题。

标签: python optimization garbage-collection


【解决方案1】:

这应该不是问题,因为您只是为列表元素分配新值,而不是真正删除它们。

但是你应该使用枚举而不是使用索引方法搜索。

另见此处: http://unspecified.wordpress.com/2009/02/12/thou-shalt-not-modify-a-list-during-iteration/ “首先,让我明确一点,在本文中,当我说“修改”时,我的意思是从列表中插入或删除项目。仅仅更新或改变列表项就可以了。”

【讨论】:

    【解决方案2】:

    您的代码存在几个问题。

    首先,将None 分配给列表元素不会删除它:

    >>> l=[1,2,3,4,5,6,6,7,8,9]
    >>> len(l)
    10
    >>> l[l.index(5)]=None
    >>> l
    [1, 2, 3, 4, None, 6, 6, 7, 8, 9]
    >>> len(l)
    10
    

    其次,使用索引来查找要更改的元素根本不是有效的方法。

    您可以使用枚举,但仍需要循环删除None 值。

    for i,a in enumerate(b):
        # Do many things with a
        b[i]=C(modified_a)
        b[i]=None 
    c=[e for e in b if e is not None]
    

    您可以使用列表推导将新的“a”值复制到 c 列表中,然后删除 b:

    c=[do_many_things(a) for a in b]
    del b                              # will still occupy memory if not deleted...
    

    或者如果你想在原地修改b,可以使用slice assignment

    b[:]=[do_many_things(a) for a in b]
    

    切片分配是这样工作的:

    #shorted a list
    >>> b=[1,2,3,4,5,6,7,8,9]
    >>> b[2:7]=[None]
    >>> b
    [1, 2, None, 8, 9]
    
    #expand a list
    >>> c=[1,2,3]
    >>> c[1:1]=[22,33,44]
    >>> c
    [1, 22, 33, 44, 2, 3]
    
    # modify in place
    >>> c=[1,2,3,4,5,6,7]
    >>> c[0:7]=[11,12,13,14,15,16,17]
    >>> c
    [11, 12, 13, 14, 15, 16, 17]
    

    您可以像这样在列表推导中使用它:

    >>> c=list(range(int(1e6)))
    >>> c[:]=[e for e in c if e<10]
    >>> c
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    其中一位 cmets 指出,切片分配并没有准确地就地修改;生成一个临时列表。 That is true. 但是,让我们看看这里的总时间:

    import time
    import random
    fmt='\t{:25}{:.5f} seconds' 
    count=int(1e5)
    a=[random.random() for i in range(count)]
    b=[e for e in a]
    
    t1=time.time()
    for e in b:
        if e<0.5: b[b.index(e)]=None  
    c=[e for e in b if e is not None]    
    print(fmt.format('index, None',time.time()-t1))
    
    b=[e for e in a]
    t1=time.time()
    for e in b[:]:
        if e<0.5: del b[b.index(e)]  
    print(fmt.format('index, del',time.time()-t1))
    
    b=[e for e in a]
    t1=time.time()
    for i,e in enumerate(b[:]):
        if e<0.5: b[i]=None
    c=[e for e in b if e is not None]    
    print(fmt.format('enumerate, copy',time.time()-t1))
    
    t1=time.time()
    c=[e for e in a if e<.5]
    del a
    print(fmt.format('c=',time.time()-t1))
    
    b=[e for e in a]
    t1=time.time()
    b[:]=[e for e in b if e<0.5]
    print(fmt.format('a[:]=',time.time()-t1))
    

    在我的电脑上打印这个:

    index, None              87.30604 seconds
    index, del               28.02836 seconds
    enumerate, copy          0.02923 seconds
    c=                       0.00862 seconds
    a[:]=                    0.00824 seconds
    

    或者,如果这没有帮助,请使用 numpy 以获得更优化的数组选项。

    【讨论】:

    • 你能解释一下这个 [:] 有什么用吗?我认为这将创建一个中间列表(分配右侧的结果),然后,如果您分配给 b 或 b[:] 并没有太大区别。
    • 我不是这里的专家,但根据这个答案,你错了:stackoverflow.com/a/4948508/1413374
    • 列表推导确实会创建一个新列表。然后,切片分配将该列表的内容复制到 b 引用的列表中。
    • 我无意删除列表元素。事实上,稍后在同一项目中的其他人的代码要求列表具有相同的长度,但将处理后的值替换为 None。这个答案也变成了一场火焰大战。另请注意,我不关心计算时间,但如问题中所述,内存使用情况。创建b 的第二个副本是不可能的。
    • @astex:如果您“无意删除列表元素”,为什么您的问题标题是“在迭代期间删除 python 列表的元素”?还请解释一下'火焰战争'?我不认为我会因为任何事情而攻击任何人。我给你的 5 个解决方案中有四个根本不涉及创建任何临时列表。 list[:] 可以,所以不要使用它。抱歉,如果对意图有一些混淆 - 我只是想帮忙。
    【解决方案3】:

    您最好的选择是generator

    def gen(b):
       for a in b:
          # Do many things with a
          yield a
    

    在这里正确完成,不需要额外的内存。

    【讨论】:

    • 如果b 在我开始生成c 之前不必完全填充,这将非常适合内存管理。也许我很困惑,但是如上所述,这仍然需要内存中bc 的完整副本,是吗?
    • 取决于您对c 的操作。如果c 仅在其他地方用作交互器,那么这很有效,不需要制作大列表的完整副本。如果您需要像c[n] 一样为c 下标,那么您需要该列表。如果内存是一个问题,最好重新考虑您的整体方法,以便可以在整个程序中使用生成器 - 如果可能的话。如果没有,请不要使用 b[b.index(a)],因为 Pyson 指出这非常慢。
    • c 是最终产品。它必须作为列表返回。此外,在a 上运行的代码依赖于 Selenium 的一个实例,因此速度非常慢。正如我所说,这不是主要的时间瓶颈(通过索引搜索当然不是)。我只需要这样做以提高内存效率。还应该注意的是,Sebastian 在 Pyson 之前提到了使用 enumerate。
    • c is the final product. It must be returned as a list 好的,做你该做的,在迭代时改变列表元素并不重要——只有列表长度。 It should also be noted that Sebastian mentioned using enumerate before Pyson.我很困惑 - 你为什么告诉我这个?我指的是 Pyson 帖子中的时间;不要试图卷入你们可能有的任何争议。
    猜你喜欢
    • 1970-01-01
    • 2018-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-21
    • 2019-05-02
    • 2011-07-04
    相关资源
    最近更新 更多