Я создал два списка l1
и l2
, но каждый со своим методом создания:
import sys
l1 = [None] * 10
l2 = [None for _ in range(10)]
print('Size of l1 =', sys.getsizeof(l1))
print('Size of l2 =', sys.getsizeof(l2))
Но результат меня удивил:
Size of l1 = 144
Size of l2 = 192
Список, созданный с пониманием списка, имеет больший размер в памяти, но оба списка в Python идентичны.
Это почему? Это какая-то внутренняя часть CPython или какое-то другое объяснение?
python
list
memory-management
python-internals
Андрей Кесели
источник
источник
144 == sys.getsizeof([]) + 8*10)
где 8 - размер указателя.10
на11
,[None] * 11
список будет иметь размер152
, но понимание списка по-прежнему будет иметь размер192
. Ранее связанный вопрос не является точным дубликатом, но он важен для понимания, почему это происходит.Ответы:
Когда вы пишете
[None] * 10
, Python знает, что ему потребуется список из ровно 10 объектов, поэтому он выделяет именно это.Когда вы используете понимание списка, Python не знает, сколько ему понадобится. Таким образом, список постепенно увеличивается по мере добавления элементов. Для каждого перераспределения он выделяет больше места, чем необходимо, так что ему не нужно перераспределять для каждого элемента. Результирующий список, вероятно, будет несколько больше, чем нужно.
Вы можете увидеть это поведение при сравнении списков, созданных с одинаковыми размерами:
Вы можете видеть, что первый метод выделяет только то, что нужно, а второй периодически увеличивается. В этом примере он выделяет достаточно для 16 элементов, и пришлось перераспределить при достижении 17-го.
источник
*
когда я знаю размер впереди.[x] * n
с неизменнымx
в вашем списке. Результирующий список будет содержать ссылки на идентичный объект.Как уже отмечалось в этом вопросе, понимание списка используется
list.append
под капотом, поэтому он вызовет метод list-resize, который перераспределяется.Чтобы продемонстрировать это себе, вы можете использовать
dis
disasembler:Обратите внимание на
LIST_APPEND
код операции при разборке<listcomp>
объекта кода. Из документов :Теперь, для операции повторения списка, у нас есть подсказка о том, что происходит, если мы рассмотрим:
Так что, похоже, можно точно выделить размер. Глядя на исходный код , мы видим, что именно это и происходит:
А именно здесь
size = Py_SIZE(a) * n;
. Остальные функции просто заполняют массив.источник
.extend()
.list.append
является амортизированной операцией с постоянным временем, потому что при изменении размера списка он перераспределяется. Поэтому не каждая операция добавления приводит к появлению вновь выделенного массива. В любом случае вопрос о том , что я связан показывает вам в исходном коде , что на самом деле, списочные сделать использованиеlist.append
,.LIST_APPEND
Ни один из них не является блоком памяти, но это не предопределенный размер. В дополнение к этому в массиве есть некоторый дополнительный интервал между элементами массива. Вы можете увидеть это сами, запустив:
Который не составляет размер l2, а скорее меньше.
И это намного больше, чем одна десятая размера
l1
.Ваши номера должны отличаться в зависимости от деталей вашей операционной системы и сведений о текущем использовании памяти в вашей операционной системе. Размер [None] никогда не может быть больше доступной смежной памяти, в которой переменная установлена для хранения, и переменная может быть перемещена, если впоследствии она динамически распределяется, чтобы быть больше.
источник
None
на самом деле не хранится в базовом массиве, хранится толькоPyObject
указатель (8 байт). Все объекты Python расположены в куче.None
является одноэлементным, поэтому наличие списка с множеством нон просто создаст массив указателей PyObject на один и тот жеNone
объект в куче (и не будет использовать дополнительную память в процессе для каждого дополнительногоNone
). Я не уверен, что вы подразумеваете под "Ни один не имеет заранее заданный размер", но это не звучит правильно. Наконец, ваш цикл сgetsizeof
каждым элементом не демонстрирует то, что вы думаете, что он демонстрирует.gestsizeof
каждогоele
изl2
них вводит в заблуждение, посколькуgetsizeof(l2)
не учитывает размер элементов внутри контейнера .l1 = [None]; l2 = [None]*100; l3 = [l2]
тогдаprint(sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))
. вы получите результат , как:72 864 72
. То есть, соответственно,64 + 1*8
,64 + 100*8
, и64 + 1*8
, опять же , предполагая систему 64 - битной с 8 байт размера указателя.sys.getsizeof
* не учитывает размер элементов в контейнере. Из документов : « Учитывается только потребление памяти, непосредственно относящееся к объекту, а не потребление памяти объектами, на которые он ссылается ... См. Рецепт рекурсивного sizeof для примера рекурсивного использования getsizeof () для определения размера контейнеров и все их содержимое. "