Я создал два списка 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 или какое-то другое объяснение?

Ответы (3)

Когда вы пишете [Нет] * 10, Python знает, что ему потребуется список из ровно 10 объектов, поэтому он выделяет именно его.

Когда вы используете понимание списка, Python не знает, сколько ему потребуется. Таким образом, список постепенно увеличивается по мере добавления элементов. Для каждого перераспределения он выделяет больше места, чем необходимо немедленно, так что ему не нужно перераспределять для каждого элемента. Результирующий список, вероятно, будет несколько больше, чем нужно.

Это поведение можно увидеть при сравнении списков, созданных с похожими размерами:

>>> sys.getsizeof([None]*15)
184
>>> sys.getsizeof([None]*16)
192
>>> sys.getsizeof([None for _ in range(15)])
192
>>> sys.getsizeof([None for _ in range(16)])
192
>>> sys.getsizeof([None for _ in range(17)])
264

Вы можете видеть, что первый метод выделяет только то, что нужно, а второй периодически увеличивается. В этом примере он выделяет достаточно для 16 элементов, и его пришлось перераспределить при достижении 17-го.

Нет - это блок памяти, но его размер не задан заранее. Вдобавок к этому в массиве есть дополнительный интервал между элементами массива. Вы можете убедиться в этом сами, запустив:

for ele in l2:
    print(sys.getsizeof(ele))

>>>>16
16
16
16
16
16
16
16
16
16

Что не составляет в сумме размер l2, а скорее меньше.

print(sys.getsizeof([None]))
72

А это намного больше одной десятой размера l1.

Ваши числа должны различаться в зависимости как от характеристик вашей операционной системы, так и от информации о текущем использовании памяти в вашей операционной системе. Размер [None] никогда не может быть больше доступной смежной памяти, в которой переменная установлена ​​для сохранения, и переменную, возможно, придется переместить, если позже она будет динамически выделена для увеличения.

Как отмечено в , этот вопрос понимание списка использует list.append под капотом, поэтому он вызовет метод изменения размера списка, который превышает доступный.

Чтобы продемонстрировать это самому себе, вы можете использовать dis disasembler:

>>> code = compile('[x for x in iterable]', '', 'eval')
>>> import dis
>>> dis.dis(code)
  1           0 LOAD_CONST               0 ( at 0x10560b810, file "", line 1>)
              2 LOAD_CONST               1 ('')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (iterable)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

Disassembly of  at 0x10560b810, file "", line 1>:
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (x)
              8 LOAD_FAST                1 (x)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE
>>>

Обратите внимание на код операции LIST_APPEND при дизассемблировании объекта кода . Из документов:

LIST_APPEND (i)

Вызовы list.append (TOS [-i], TOS). Используется для реализации понимания списков.

Теперь, для операции повторения списка, у нас есть подсказка о том, что происходит, если мы рассмотрим:

>>> import sys
>>> sys.getsizeof([])
64
>>> 8*10
80
>>> 64 + 80
144
>>> sys.getsizeof([None]*10)
144

Итак, похоже, можно точно выделить размер. Глядя на исходный код, мы видим, что именно это и происходит:

static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
    Py_ssize_t i, j;
    Py_ssize_t size;
    PyListObject *np;
    PyObject **p, **items;
    PyObject *elem;
    if (n < 0)
        n = 0;
    if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
        return PyErr_NoMemory();
    size = Py_SIZE(a) * n;
    if (size == 0)
        return PyList_New(0);
    np = (PyListObject *) PyList_New(size);

А именно здесь: size = Py_SIZE (a) * n;. Остальные функции просто заполняют массив.

2022 WebDevInsider