python中生成器对象的大小

时间:2015-01-06 07:49:21

标签: python generator python-internals

以下代码:

import sys
x=(i for i in range(1,11))
print x


print 'Before starting iterating generator size is' ,sys.getsizeof(x)

print 'For first time'
for i in x:
    print i

print 'For second time , does not print anything'    
for i in x:
    print i # does not print anything

print 'After iterating generator size is' ,sys.getsizeof(x)

输出是:

<generator object <genexpr> at 0x014C1A80>
Before starting iterating generator size is 40
For first time
1
2
3
4
5
6
7
8
9
10
For second time
After iterating generator size is 40

生成器对象的大小最初是40,当我完成迭代时它仍然是40.但是没有元素从第二个循环引用。

为什么生成器对象在创建时会占用相同的内存,并且在完成迭代时会占用相同的内存?

2 个答案:

答案 0 :(得分:5)

生成器占用内存的空间只是簿记信息。其中保留了对框架对象的引用(管理正在运行的Python代码,例如本地代码),现在它正在运行,并保留对代码对象的引用。没有更多:

>>> x=(i for i in range(1,11))
>>> dir(x)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']
>>> x.gi_frame
<frame object at 0x1053b4ad0>
>>> x.gi_running
0
>>> x.gi_code
<code object <genexpr> at 0x1051af5b0, file "<stdin>", line 1>

这只是3个引用,加上通常的Python对象类型信息(思考引用计数)和弱引用列表;这是大约4个指针,一个整数和一个结构,在你的系统上占用40个字节(在我的系统上,64位OS X,它是80个字节)。 sys.getsizeof()报告了 在C中实现的结构的大小,并且不会对指针进行递归。

因此,当您运行生成器时,该内存量不会改变。引用的帧可能会改变使用的内存量(如果生成器表达式引用大对象朝向一端或另一端)但您不会在生成器对象上看到sys.getsizeof()的结果;相反,请查看框架本地:

>>> next(x)
1
>>> x.gi_frame.f_locals
{'i': 1, '.0': <listiterator object at 0x105339dd0>}

.0对象是生成器在range()循环中使用的for迭代器,ifor循环目标。 listiterator是另一个可迭代对象,它具有对生成的列表range()的私有引用以及位置计数器,因此每次请求时它都可以生成下一个元素。

您无法查询生成器的元素大小;无论如何,它们都会根据需要生成元素,你不能事先“知道”它们会产生多少,也不能知道它们在运行后产生了多少。 sys.getsizeof()当然不会告诉你;它无论如何都是一种测量内存占用量的工具,如果你想知道足迹,你必须递归测量所有引用的对象。

可以看到生成器已从帧中完成其运行;完成后清除

>>> x.gi_frame
<frame object at 0x1053b4ad0>
>>> list(x)
[2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> x.gi_frame is None
True

所以最后,用于生成器的内存驻留在框架中的结构中(本地,可能是全局,这些名称空间中的每个对象可能再次引用其他对象),并且当生成器完成时,框架被清除并且生成器.gi_frame指针被更改为指向None单例,如果引用计数已降为0,则将帧清除。

所有这些仅适用于生成器,而不适用于一般的迭代;生成器是Python代码,因此可以深入反思。

答案 1 :(得分:2)

生成器x基本上是一个函数,只要调用它就会提供i的下一个值。它不会提前计算所有值。它等待它被调用,然后它计算并提供下一个值。

因此每次调用都会产生下一个值。

为什么x的大小不会改变?嗯,这是因为x不是数字列表。在过程的开始和结束时,它仍然是相同的功能。

这是使用发电机的优势。您不必在开始时将所有内容加载到内存中(因此它可以节省内存),并且(如果正确完成)您不必在实际需要之前计算任何内容(因此,如果某些值可以节省计算时间)不需要)。

要看到这个:

x = (i for i in xrange(10**10))
for i in x:
    print i
    if i>10:
        break
print 'intermission'
for i in x:
    print i
    if i>20:
        break

(注意xrange,而非range ---使用range会导致提前计算发生)。想象一下,实际生成从0到10**10的整数需要多长时间以及需要多少内存。比较此代码的运行速度。