Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/348.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python itertools.chain()是否允许从已消耗的项目中释放内存_Python_Memory_Iteration_Itertools - Fatal编程技术网

Python itertools.chain()是否允许从已消耗的项目中释放内存

Python itertools.chain()是否允许从已消耗的项目中释放内存,python,memory,iteration,itertools,Python,Memory,Iteration,Itertools,我试图通过成批运行大型查询来减少Django应用程序中的内存使用。尽管我认为自己很聪明,但内存使用量一直在增长,直到进程最终被终止。我目前有一个理论认为查询集不会被垃圾收集,我想知道这是否与我如何批处理查询有关 def grouper(iterable, n, fillvalue=None): "Collect data into fixed-length chunks or blocks" args = [iter(iterable)] * n return zip_l

我试图通过成批运行大型查询来减少Django应用程序中的内存使用。尽管我认为自己很聪明,但内存使用量一直在增长,直到进程最终被终止。我目前有一个理论认为查询集不会被垃圾收集,我想知道这是否与我如何批处理查询有关

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

def get_data(pks):
    groups = list(grouper(pks, batch_size))
    querysets = [_queryset(pks) for pks in groups]
    return itertools.chain(*querysets)

for item in get_data([..big list of primary keys..]):
    process(item)
假设在
get\u data
函数中生成了3个查询集。当我消耗完第一个查询集的所有项目后,该查询集会被释放吗?或者从技术上说我还从连锁店那里得到了它的参考

我不确定内存是否保存在这里(它可能是数据库驱动程序,Django本身内部的东西,等等),但它似乎是一个不错的候选者。有没有好的工具可以测量对象类型的内存使用情况?这段特定的代码目前正在Python2上运行

我应该注意到我是从ipython外壳上运行的,以防万一

编辑:

看来链在这里不负责任。我添加了一些代码来打印每个类的对象计数,并且模型对象的计数保持不变

import gc

def get_object_counts():
    from collections import Counter
    classes = []
    for obj in gc.get_objects():
        if hasattr(obj, '__class__'):
            classes.append(str(obj.__class__))
    return Counter(classes)
然后在特定的时间间隔(批量大小):

为了完成,这里是前9名。我认为罪魁祸首是
django.db.models.base.ModelState
,它一直在增长,没有被收集

第一:

("<type 'dict'>", 59184)
("<type 'list'>", 48710)
("<type 'function'>", 48300)
("<type 'tuple'>", 38920)
("<type 'cell'>", 10203)
("<type 'weakref'>", 9957)
("<type 'set'>", 7230)
("<type 'type'>", 5947)
("<class 'django.db.models.base.ModelState'>", 4682)
(“”,59184)
("", 48710)
("", 48300)
("", 38920)
("", 10203)
("", 9957)
("", 7230)
("", 5947)
("", 4682)
第二:

("<type 'dict'>", 59238)
("<type 'list'>", 48730)
("<type 'function'>", 48315)
("<type 'tuple'>", 38937)
("<type 'cell'>", 10207)
("<type 'weakref'>", 9959)
("<type 'set'>", 7230)
("<type 'type'>", 5950)
("<class 'django.db.models.base.ModelState'>", 4696)
(“”,59238)
("", 48730)
("", 48315)
("", 38937)
("", 10207)
("", 9959)
("", 7230)
("", 5950)
("", 4696)

不确定,但您可以通过将临时列表转换为iterables/Generator来避免创建临时列表(并在源位置解决分配问题):

def get_data(pks):
    groups = grouper(pks, batch_size)  # turn off explicit list conversion
    querysets = (_queryset(pks) for pks in groups)  # gencomp not listcomp
    return itertools.chain.from_iterable(querysets)  # nicer with "from_iterable"

我自己也看到过类似的行为,似乎
itertools.chain
(和
itertools.chain.from_iterable
)会保留对作为参数传递的任何内容的引用,直到它们停止迭代。我想这就是django查询集及其缓存结果没有被垃圾收集的原因。这似乎是python2和python3上的行为,也是您从任何用户定义的python函数中看到的行为(请参阅)。也许C语言库中的函数(如itertools)在退出之前可以更自由地删除对参数的引用,但显然它们并不选择这样做

作为一种解决方法,您可以通过使用
iter
itertools.chain
本身,在迭代器中将各个参数包装到
itertools.chain
。看起来,一旦这些迭代器耗尽,它们就会删除对底层iterable的引用,并允许对其进行垃圾收集

最后要注意的是,即使完全使用结果也不足以释放内存——在释放迭代器(或子迭代器)使用的内存之前,必须将控件再次返回给迭代器(或链中的任何子迭代器)。同样,这也是您从普通python函数中所期望的

下面的代码显示了这一切的发生:

from __future__ import print_function
import itertools
import gc

def print_whats_left_after(num, numbers_iter):
    """ Read three numbers and print what pairs haven't been gc'd """
    for _ in range(num):
        next(numbers_iter, None)
    gc.collect()
    # Print integer pairs that were not garbage collected
    print(sorted([o for o in gc.get_objects()
                  if isinstance(o, list) and len(o) == 2 and
                  all(isinstance(i, int) for i in o)]))

print_whats_left_after(2, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(4, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(5, itertools.chain([1, 2], [3, 4]))
# -> []

print_whats_left_after(2, itertools.chain.from_iterable([[1, 2], [3, 4]]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain.from_iterable([[1, 2], [3, 4]]))
# -> [[1, 2], [3, 4]]

print_whats_left_after(2, itertools.chain(itertools.chain([1, 2]), [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain(itertools.chain([1, 2]), [3, 4]))
# -> [[3, 4]]  # [1, 2] was gc'd!!!

print_whats_left_after(2, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[3, 4]]  # [1, 2] was gc'd!!!
print_whats_left_after(4, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[3, 4]]
print_whats_left_after(5, itertools.chain(iter([1, 2]), [3, 4]))
# -> []

def arg_clobberer(arg):
    arg = None
    yield
print_whats_left_after(0, arg_clobberer([1, 2]))
# -> [[1, 2]]
print_whats_left_after(1, arg_clobberer([1, 2]))
# -> []

def arg_deleter(arg):
    del arg
    yield
print_whats_left_after(0, arg_deleter([1, 2]))
# -> [[1, 2]]
print_whats_left_after(1, arg_deleter([1, 2]))
# -> []

希望这有帮助

grouper
函数取自itertools recipes doc:listcomp的范围是否与[]*n相同?对于不可变对象是的,但我的构造会为每个元素生成一个不同的iterable。也许我错了,特别是如果它是来自食谱的代码。暂时别提了。谢谢,这真是有用的信息。将参数包装到其他生成器中的好技巧。我最终重构了原始代码,因此无法对其进行实际测试,但我认为您已经充分回答了问题。
from __future__ import print_function
import itertools
import gc

def print_whats_left_after(num, numbers_iter):
    """ Read three numbers and print what pairs haven't been gc'd """
    for _ in range(num):
        next(numbers_iter, None)
    gc.collect()
    # Print integer pairs that were not garbage collected
    print(sorted([o for o in gc.get_objects()
                  if isinstance(o, list) and len(o) == 2 and
                  all(isinstance(i, int) for i in o)]))

print_whats_left_after(2, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(4, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(5, itertools.chain([1, 2], [3, 4]))
# -> []

print_whats_left_after(2, itertools.chain.from_iterable([[1, 2], [3, 4]]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain.from_iterable([[1, 2], [3, 4]]))
# -> [[1, 2], [3, 4]]

print_whats_left_after(2, itertools.chain(itertools.chain([1, 2]), [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain(itertools.chain([1, 2]), [3, 4]))
# -> [[3, 4]]  # [1, 2] was gc'd!!!

print_whats_left_after(2, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[3, 4]]  # [1, 2] was gc'd!!!
print_whats_left_after(4, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[3, 4]]
print_whats_left_after(5, itertools.chain(iter([1, 2]), [3, 4]))
# -> []

def arg_clobberer(arg):
    arg = None
    yield
print_whats_left_after(0, arg_clobberer([1, 2]))
# -> [[1, 2]]
print_whats_left_after(1, arg_clobberer([1, 2]))
# -> []

def arg_deleter(arg):
    del arg
    yield
print_whats_left_after(0, arg_deleter([1, 2]))
# -> [[1, 2]]
print_whats_left_after(1, arg_deleter([1, 2]))
# -> []