Размер объекта генератора в питоне
Для следующего кода:
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 ответа
Пространство, которое генератор занимает в памяти, - это просто бухгалтерская информация. В нем сохраняется ссылка на объект фрейма (администрация для работающего кода 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
петли, i
это for
петля цель. 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
singleton, оставляя очищаемый кадр, если счетчик ссылок уменьшился до 0.
Все это относится только к генераторам, а не к итерируемым в целом; генераторы - это код Python, и, таким образом, их можно глубоко изучить.
Генератор 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
и сколько памяти это займет. Сравните, насколько быстро работает этот код.