【问题标题】:size of generator object in pythonpython中生成器对象的大小
【发布时间】:2015-03-03 20:39:30
【问题描述】:

对于以下代码:

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。但是第二个循环没有引用任何元素。

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

【问题讨论】:

    标签: python generator python-internals


    【解决方案1】:

    生成器在内存中占用的空间只是簿记信息。在其中保留了对框架对象的引用(管理正在运行的 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 对象是生成器在for 循环中使用的range() 迭代器,ifor 循环目标。 listiterator 是另一个可迭代对象,它具有对生成的列表 range() 的私有引用以及位置计数器,因此它可以在您每次请求时产生下一个元素。

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

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

    >>> 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,则要清除帧。

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

    【讨论】:

      【解决方案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 的整数需要多长时间以及需要多少内存。比较这段代码的运行速度。

      【讨论】:

        猜你喜欢
        • 2012-09-10
        • 2010-10-31
        • 2013-01-05
        • 1970-01-01
        • 1970-01-01
        • 2020-03-09
        • 2010-11-19
        • 1970-01-01
        相关资源
        最近更新 更多