Python - 列表理解中的 append() 操作导致内存气球

Python - append() operation in list comprehension resulting in memory balloon

Python 3.5.2 (default, Nov 23 2017, 16:37:01) 
[GCC 5.4.0 20160609] on linux
2 GB RAM, 4 GB Swap
----
import re
fieldname_groups = ("(ip,clienthost,ip_addr)", "(username,user,id)")

srcgroup = [x.strip() for x in re.sub('[\(\)]+', '', fieldname_groups[0]).split(',')]
dstgroup = [x.strip() for x in re.sub('[\(\)]+', '', fieldname_groups[1]).split(',')]
if not srcgroup or not dstgroup: raise Exception("No srcgroup or dstgroup specified!")

# srcgroup is now ['ip', 'clienthost', 'ip_addr']
# dstgroup is now ['username', 'user', 'id']

# Now append the string '.keyword' to a copy of every string in each list
[srcgroup.append('%s.keyword' % x) for x in srcgroup]
[dstgroup.append('%s.keyword' % x) for x in dstgroup]

# srcgroup should now be ['ip', 'clienthost', 'ip_addr', 'ip.keyword', 'clienthost.keyword', 'ip_addr.keyword']
# dstgroup should be similar

每次我 运行 这段代码,一旦我点击列表理解,内存就会膨胀并且进程被终止。

我不明白这里的问题是什么;我觉得这是我一直在做的事情,但这次它不起作用,所以这可能是一个业余错误,但我很感激任何帮助来解决它。我什至尝试重写代码以使用标准的 for 循环,但它仍然爆炸。

谢谢!

正如@Andrej Kesely 所说,您在迭代 时向正在迭代的列表添加元素。因此,您不断向循环中添加元素,因此循环永远不会结束。

我不确定你到底想要什么,但也许你应该做的是动态创建一个新列表来迭代它,而不是你要添加元素的列表,这样:

[srcgroup.append('%s.keyword' % x) for x in srcgroup[:]]  # Notice the [:]
[dstgroup.append('%s.keyword' % x) for x in dstgroup[:]]  # Notice the [:]

在列表名称的末尾添加 [:] 切片,保留所有元素,并即时将其保存到新对象中,因此不会发生错误。

你正在添加一个列表,同时你正在迭代它 -> 内存将无限增长:

[srcgroup.append('%s.keyword' % x) for x in srcgroup]
[dstgroup.append('%s.keyword' % x) for x in dstgroup]

解决方案是迭代一个副本:

[srcgroup.append('%s.keyword' % x) for x in srcgroup[:]]
[dstgroup.append('%s.keyword' % x) for x in dstgroup[:]]

编辑:

更好的办法是稍微简化一下(根据以下答案):

srcgroup = ['ip', 'keyword']

srcgroup.extend([f'{x}.keyword' for x in srcgroup])
print(srcgroup)

将输出:

['ip', 'keyword', 'ip.keyword', 'keyword.keyword']

在列表理解中,您应该有副作用(例如将项目附加到列表)。列表理解创建一个新列表,您不需要手动 append 项。

如果要替换列表中的每个元素,可以创建一个新列表并替换旧列表:

scrgroup = [('%s.keyword' % x) for x in srcgroup]
dstgroup = [('%s.keyword' % x) for x in dstgroup]

但是,如果您想将具有该格式的每个元素的副本添加到列表中,则需要对其进行扩展:

scrgroup.extend([('%s.keyword' % x) for x in srcgroup])
dstgroup.extend([('%s.keyword' % x) for x in dstgroup])

但是,为了减少内存使用,您可以在这种情况下使用生成器:

scrgroup.extend((('%s.keyword' % x) for x in srcgroup))
dstgroup.extend((('%s.keyword' % x) for x in dstgroup))

扩展列表的正确方法是使用 extend 方法。

srcgroup = [x.strip() for x in re.sub('[\(\)]+', '', fieldname_groups[0]).split(',')]
srcgroup.extend(['%s.keyword' % x) for x in srcgroup])

请注意,extend 的参数是一个新列表,而不是在向 srcgroup 添加新元素时迭代 srcgroup 的生成器表达式,这一点很重要。

在任何情况下,您都不应仅出于表达式的副作用而使用列表理解。使用

for x in ...:
    ...

而不是

[... for x in ...]

如上所述,您的问题是您要附加到正在迭代的集合。既然如此,复制整个列表就有些浪费了。为避免创建列表的另一个副本,您可以这样做:

srcgroup.extend( ['%s.keyword' % x for x in srcgroup] )
dstgroup.extend( ['%s.keyword' % x for x in dstgroup] )

这将就地修改列表。这是性能比较:

timeit.timeit('arr = range(1000); arr = arr.extend( [x + 1 for x in arr] )')
53.47952483119644
timeit.timeit('arr = range(1000); arr = [arr.append(x + 1) for x in arr[:]]')
118.02281077109734

如您所见,我的建议只用了一半的时间(尽管它们仍然是 O(n))。