关于python:使用yield的函数有多少内存?

How much memory does a function with yield use?

我很难理解yield关键字。我理解程序执行时的效果,但我不真正理解它使用了多少内存。

我会用例子来解释我的疑问。假设我们有三个功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HUGE_NUMBER = 9223372036854775807

def function1():
    for i in range(0, HUGE_NUMBER):
        yield i

def function2():
    x = range(0, HUGE_NUMBER)
    for i in x:
        yield i

def function3(file):
    with open(file, 'r') as f:
        dictionary = dict(csv.reader(f, delimiter = ' '))
    for k,v in dictionary.iteritems():
        yield k,v

如果我迭代第一个函数返回的生成器,那么这个巨大的范围是否实际存储在内存中?

第二个函数呢?

如果我迭代第三个函数返回的生成器(而不仅仅是生成字典并直接迭代它),我的程序会使用更少的内存吗?


python 2 range()函数生成的大列表需要存储,是的,并且在生成器函数的整个生命周期中将占用内存。

如果生成的结果是根据需要计算出来的,那么生成函数可以节省内存,但是range()函数会提前生成所有结果。

你可以计算下一个数字:

1
2
3
4
5
def function1():
    i = 0
    while i < HUGE_NUMBER:
        yield i
        i += 1

你会得到同样的结果,但你不会一次存储整个范围的所有数字。这基本上就是循环遍历xrange()对象所做的;它根据请求计算数字。(在python 3中,xrange()取代了range()

这同样适用于您的function3;您首先将整个文件读到一个字典中,这样在您迭代时,它仍然存储在内存中。不需要将整个文件读取到内存中,只需要在之后生成每个元素。您可以循环文件和收益行:

1
2
3
4
5
6
7
8
9
10
def function3(file):
    seen = set()
    with open(file, 'r') as f:
        reader = csv.reader(f, delimiter = ' ')
        for k, v in reader:
            if k in seen:
                # already seen
                continue
            seen.add(k)
            yield k, v

这只存储为避免重复而看到的键(就像字典那样),但不存储值。当您在生成器上迭代时,内存会增加。如果重复项不是问题,则可以完全忽略跟踪看到的键:

1
2
3
4
5
def function3(file):
    with open(file, 'r') as f:
        reader = csv.reader(f, delimiter = ' ')
        for k, v in reader:
            yield k, v

甚至

1
2
3
4
def function3(file):
    with open(file, 'r') as f:
        reader = csv.reader(f, delimiter = ' ')
        return reader

毕竟,读者是无可辩驳的。


生成器对象包含对函数作用域的引用,并通过扩展包含函数内所有本地对象。减少内存使用的方法是在每一个可能的级别上使用迭代器,而不仅仅是在顶层。


如果您想检查一个对象使用了多少内存,您可以将本文作为一个代理。我觉得它很有用。

"试试这个:

1
sys.getsizeof(object)

getsizeof()调用对象的sizeof方法,如果对象由垃圾收集器管理,则会添加额外的垃圾收集器开销。"

递归配方