← Назад к вопросам

Как выделяется память под элементы в списке в Python?

2.0 Middle🔥 201 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Выделение памяти под элементы списка в Python

Это сложный вопрос, который требует понимания того, как Python управляет памятью. В Python всё — это объекты, и список хранит не сами значения, а ссылки на объекты.

Концепция: Object Reference Model

Python использует модель ссылок на объекты, а не прямое хранение значений:

my_list = [1, 2, 3]

# На самом деле это выглядит так:
# my_list = [<ref to int(1)>, <ref to int(2)>, <ref to int(3)>]

# Проверим это
print(id(1))  # 140734829412320 - адрес объекта int(1)
print(id(my_list[0]))  # Тот же адрес!

# Это значит, что 1 — это ОДИН объект в памяти
# А список хранит ссылку на этот объект

Структура списка в памяти

# Список — это объект Python, который содержит:
# 1. Размер массива
# 2. Количество элементов
# 3. Массив указателей (PyObject*) на элементы

# На C уровне это выглядит примерно так:
# typedef struct {
#     Py_ssize_t ob_size;  // Количество элементов
#     PyObject **ob_item;  // Массив указателей
# } PyListObject;

my_list = [1, 2, 3]
# Выделяется память для:
# 1. Объекта PyListObject (структура)
# 2. Массива указателей (может быть больше, чем 3 элемента из-за over-allocation)
# 3. Сами объекты int(1), int(2), int(3) находятся в другом месте памяти

Over-allocation (Перевыделение памяти)

Список выделяет БОЛЬШЕ памяти, чем нужно, для оптимизации операций append:

import sys

my_list = []
print(f"Пусто: {sys.getsizeof(my_list)} байт")

for i in range(10):
    my_list.append(i)
    size = sys.getsizeof(my_list)
    capacity = len(my_list)
    allocated_slots = (size - 56) // 8  # 56 байт на структуру, 8 байт на указатель
    print(f"Элементов: {capacity}, Выделено слотов: {allocated_slots}")

# Вывод примерно:
# Элементов: 1, Выделено слотов: 1
# Элементов: 2, Выделено слотов: 2
# Элементов: 3, Выделено слотов: 3
# Элементов: 4, Выделено слотов: 4
# Элементов: 5, Выделено слотов: 8  <- Вот здесь произошло перевыделение!
# Элементов: 6, Выделено слотов: 8
# Элементов: 7, Выделено слотов: 8
# Элементов: 8, Выделено слотов: 8
# Элементов: 9, Выделено слотов: 16
# Элементов: 10, Выделено слотов: 16

Формула роста примерно: new_capacity = capacity + (capacity >> 3) + 3

Память для самих объектов

Объекты хранятся отдельно в heap'е:

import sys

# Каждый объект в Python занимает память
print(sys.getsizeof(1))          # 28 байт (int объект)
print(sys.getsizeof("hello"))    # 54 байта (string объект)
print(sys.getsizeof([1, 2, 3]))  # 88 байт (list структура)

# Интересная особенность: маленькие целые числа закэшированы
my_list = [1, 1, 1]
print(id(my_list[0]))  # 140734829412320
print(id(my_list[1]))  # 140734829412320 - ОДИН И ТОТ ЖЕ адрес!
print(id(my_list[2]))  # 140734829412320

# Это потому, что Python закэшует целые числа от -5 до 256
print(id(257) == id(257))  # False! Разные объекты

Визуальное представление

┌─────────────────────────────────────┐
│ my_list = [10, "hello", 3.14]      │
└─────────────────────────────────────┘
           ↓
┌─────────────────────────────────────┐
│ PyListObject (в памяти)             │
├─────────────────────────────────────┤
│ ob_size: 3                          │
│ ob_item: 0x140734829412320          │
└─────────────────────────────────────┘
           ↓
┌──────────┬──────────┬──────────────┐
│ Массив указателей на элементы     │
├──────────┼──────────┼──────────────┤
│ Ref to   │ Ref to   │ Ref to       │
│ int(10)  │ str()    │ float(3.14)  │
└──────────┴──────────┴──────────────┘
   ↓          ↓            ↓
┌────┐   ┌──────────┐  ┌──────────┐
│ 10 │   │ "hello" │  │  3.14    │
└────┘   └──────────┘  └──────────┘
(отдельные объекты в памяти)

Dynamically resizable array (Переменный размер)

# Когда мы добавляем элемент, а памяти нет:
my_list = [1, 2, 3]  # Выделено 4 слота

# Операция append когда слотов нет:
my_list.append(4)     # Перераспределение!
# Python:
# 1. Выделяет НОВЫЙ массив (большего размера)
# 2. Копирует ВСЕ указатели из старого массива
# 3. Добавляет новый элемент
# 4. Освобождает старый массив
# 5. Обновляет указатель my_list.ob_item на новый массив

Дефрагментация памяти

В Python есть garbage collector, который управляет памятью:

import gc

# Когда объект удаляется и на него больше нет ссылок
my_list = [1, 2, 3]
del my_list  # Объект удалён, память освобождена

# Garbage collector запускается автоматически
gc.collect()  # Вручную запустить сборку мусора

# Проверить утечки памяти
gc.get_stats()

Сравнение с хранением значений

# Python: Хранит ссылки
my_list = [1, 1, 1]  # Три ссылки на ОДН объект int(1)

# C: Хранит значения
# int array[3] = {1, 1, 1}  // Три отдельные ячейки с значением 1

# Поэтому Python медленнее, но гибче
my_list[0] = "строка"  # Можем менять тип!

Reference Counting (Подсчёт ссылок)

import sys

obj = "hello"
print(sys.getrefcount(obj))  # Количество ссылок на объект

my_list = [obj, obj, obj]
print(sys.getrefcount(obj))  # Увеличилось на 3

del my_list
print(sys.getrefcount(obj))  # Уменьшилось

Когда refcount становится 0, объект удаляется и память освобождается.

Оптимизация памяти

# Плохо: много объектов
big_list = [i for i in range(1000000)]

# Лучше: генератор (ленивое вычисление)
big_gen = (i for i in range(1000000))

# Ещё лучше: range (очень эффективно)
big_range = range(1000000)

import sys
print(sys.getsizeof(big_list))   # Много МБ
print(sys.getsizeof(big_gen))    # Несколько байт
print(sys.getsizeof(big_range))  # Несколько байт

Заключение

Память под элементы списка выделяется следующим образом:

  1. Объект списка занимает фиксированное место (структура PyListObject)
  2. Массив указателей выделяется с over-allocation для эффективности
  3. Сами элементы хранятся отдельно в heap'е, как отдельные объекты
  4. Reference counting отслеживает, используется ли объект
  5. Garbage collector освобождает неиспользуемую память

Это делает Python гибким, но требует больше памяти, чем языки с прямым хранением значений.

Как выделяется память под элементы в списке в Python? | PrepBro