如何从生成器中生成一个对象并立即将其忘记,从而不占用内存?
例如,在以下函数中:
def grouper(iterable, chunksize):
"""
Return elements from the iterable in `chunksize`-ed lists. The last returned
element may be smaller (if length of collection is not divisible by `chunksize`).
>>> print list(grouper(xrange(10), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
"""
i = iter(iterable)
while True:
chunk = list(itertools.islice(i, int(chunksize)))
if not chunk:
break
yield chunk
我不希望函数在产生对chunk
的引用后保留该引用,因为它不会被进一步使用,只会消耗内存,即使所有外部引用都已消失。
编辑:使用Python.org上的标准Python 2.5/2.6/2.7。
解决方案(几乎由@phihag和@Owen同时提出):将结果包装成一个(小)可变对象,并匿名返回块,只留下小容器:
def chunker(iterable, chunksize):
"""
Return elements from the iterable in `chunksize`-ed lists. The last returned
chunk may be smaller (if length of collection is not divisible by `chunksize`).
>>> print list(chunker(xrange(10), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
"""
i = iter(iterable)
while True:
wrapped_chunk = [list(itertools.islice(i, int(chunksize)))]
if not wrapped_chunk[0]:
break
yield wrapped_chunk.pop()
使用此内存优化,现在可以执行以下操作:
for big_chunk in chunker(some_generator, chunksize=10000):
... process big_chunk
del big_chunk # big_chunk ready to be garbage-collected :-)
... do more stuff
@半径
有几点使我在这方面感到困惑。我意识到我错过了理解的基础:什么是你的问题。
现在我想我已经明白了,请你确认。
我会那样代表你的代码
你的问题是,即使是在
#删除后,代码继续执行,需要时间来执行
以及
在B之前,
已删除名称为“A”的对象仍然存在并占用大量内存,因为此对象与生成器函数中的标识符“chunk”之间仍然存在绑定?
请原谅我问你这个显而易见的问题。
但是,由于一次在线程中出现了某种混乱,我希望您确认我现在已经正确地理解了您的问题。
是的。
@菲哈格
你在评论中写道:
(顺便说一句,我不会写所以,而是写'because')
我认为这种肯定是有争议的 事实上,我确信这是假的。但是,如果我们也考虑到你在回答的开头所说的话,你的伪装就有微妙之处,不仅仅是在这段引语中,而是在全球范围内。
让我们把东西收拾好。
下面的代码似乎证明了与您的肯定相反,在yield chunk之后,没有办法从这个函数访问chunk中存储的值
结果
是的。
不过,你也写了(这是你答案的开始):
这一次,您不会说函数在
yield chunk
之后不再持有chunk的引用,而是说在while
循环的下一轮中更新chunk之前,它的值再次是未使用。没错,在Radim的代码中,在下一轮循环中,在指令chunk = list(itertools.islice(i, int(chunksize)))
中重新分配标识符“chunk”之前,对象“chunk”不会再次使用。是的。
这段引文中的肯定2不同于前面的肯定1,有两个逻辑结果:
首先,我上面的代码无法向像您这样的人严格证明,在
yield chunk
指令之后确实有方法访问chunk的值。因为我上面代码中的条件与您确认相反的条件不同,也就是说:在您所说的Radim代码中,对象chunk在下一回合之前确实没有再次使用。
然后,我们可以假设这是因为在我上面的代码中使用了chunk(指令
print 'end of turn',id(chunk),'\n'
、print 'new turn ',id(chunk)
和last = chunk[-1]
确实使用了它),在yield chunk
之后,对对象chunk的引用仍然保持。其次,进一步推理,收集您的两个引文,得出结论,您认为这是因为在Radim代码中的
yield chunk
指令之后,chunk不再使用,因此没有对它进行任何引用。这是一个逻辑问题,IMO:没有对对象的引用是其释放的条件,因此如果你假装内存是从对象中释放的,因为它不再被使用,这相当于假设内存从对象中释放出来,因为它的失业使得interpreter在函数中删除对它的引用。
我总结:
假设在Radim的代码中,chunk在
yield chunk
之后不再使用,那么对它的引用就不再是hold,然后。。。。。cpython 2.7不会这么做。。。但是pypy 1.6使用默认的gc从对象块中释放内存。在这一点上,我对这个结果的原因感到非常惊讶:这是因为rong>chunk不再使用pypy 1.6来释放它。你并没有清楚地表达出这种推理,但没有它,我会发现你在这两个引文中所说的是不合逻辑和不可理解的。
令我困惑的是,这一结论以及我不同意这一切的原因是,它意味着pypy 1.6能够分析代码并检测出在
yield chunk
之后,块不会被再次使用。我觉得这个想法完全难以置信,我希望你:来解释你到底是怎么想的。我理解你的想法错在哪里?
如果你有证据表明,至少pypy 1.6在不再使用时不包含对块的引用。
Radim的初始代码的问题是,由于对象的引用仍然保留在生成器函数中,因此对象的持久性消耗了太多内存:这是存在这样一个持久性引用的间接症状。
你观察过Pypy1.6的类似行为吗?我看不出另一种方法来证明生成器中剩余的引用,因为根据您的引用2,在
yield chunk
之后使用chunk就足以触发对它的引用。这是一个类似于量子力学的问题:测量一个粒子的速度会改变它的速度。。。。。在
yield chunk
之后,变量值将不再在函数中使用,因此一个好的解释器/垃圾收集器将已经为垃圾收集释放chunk
(注意:cpython 2.7似乎不执行此操作,pypy 1.6使用默认gc执行此操作)。因此,除了代码示例之外,您不必更改任何内容,因为它缺少grouper
的第二个参数。注意,垃圾收集在Python中是不确定的。不收集空闲对象的null垃圾收集器是完全有效的垃圾收集器。从Python manual:
因此,如果不指定Python实现和垃圾收集器,就无法确定Python程序是否占用内存。给定特定的Python实现和垃圾收集器,可以使用^{} 模块来test是否释放对象。
这就是说,如果您真的不想从函数中引用(不一定意味着对象将被垃圾收集),下面是如何做到的:
与列表不同,您还可以使用任何其他数据结构,这些数据结构带有一个删除并返回对象的函数,如Owen's wrapper。
如果你真的想得到这个功能,我想你可以使用一个包装器:
可以像
这与phihag对
pop()
;所做的基本相同)相关问题 更多 >
编程相关推荐