← Назад к вопросам
Что происходит при добавлении элемента в заполненный массив списка в Python?
1.7 Middle🔥 161 комментариев
#DevOps и инфраструктура#Django
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Добавление элемента в заполненный список Python
Простой ответ
Python списки динамические — они автоматически растут. Когда добавляешь элемент в заполненный список, он выделяет больше памяти и добавляет элемент.
Как работает список в памяти
# Список в Python имеет две характеристики:
# - length (размер — сколько элементов сейчас)
# - capacity (вместимость — сколько может вместить до переаллокации)
my_list = [1, 2, 3]
# Внутри (CPython):
struct PyListObject {
Py_ssize_t ob_size; // length = 3
PyObject **ob_item; // указатель на массив
Py_ssize_t allocated; // capacity > length
}
# Память (схематично):
# [1] [2] [3] [?] [?] [?] ← есть место для ещё 3 элементов
# ↑ ↑ ↑
# Используется (3 элемента)
# ↑
# Выделено (6 мест)
Пошаговый процесс: my_list.append(4)
Сценарий 1: Есть место в памяти
my_list = [1, 2, 3]
# Состояние памяти:
# Выделено: 6 мест
# Используется: 3
my_list.append(4)
# 1. Проверяет: есть ли место?
# allocated (6) > ob_size (3)? ДА
# 2. Простое добавление элемента
# ob_item[3] = 4
# ob_size += 1 → 4
# 3. Результат
# [1] [2] [3] [4] [?] [?]
# Используется: 4, Выделено: 6
# Сложность: O(1) — константное время!
Сценарий 2: Памяти не хватает
my_list = [1, 2, 3, 4, 5, 6]
# Состояние:
# Используется: 6, Выделено: 6 (ПОЛНО!)
my_list.append(7)
# 1. Проверяет: есть ли место?
# allocated (6) > ob_size (6)? НЕТ
# 2. Нужна переаллокация
# Выделяет новую память большего размера
# new_capacity = old_capacity + (old_capacity >> 3)
# new_capacity = 6 + 6//8 = 6 + 0 = 6... (минимум +1)
# Но в CPython это примерно:
# new_capacity = 9 (в CPython используется формула: n + n//8 + 6)
# 3. Копирует старые данные
# new_memory = [1] [2] [3] [4] [5] [6] [?] [?] [?]
# 4. Освобождает старую память
# free(old_memory)
# 5. Добавляет новый элемент
# new_memory[6] = 7
# ob_size = 7
# Результат:
# [1] [2] [3] [4] [5] [6] [7] [?] [?]
# Используется: 7, Выделено: 9
# Сложность: O(n) — приходится копировать всё!
Стратегия роста списка
# CPython использует амортизированный рост:
# При нехватке памяти увеличивает на ~12.5%
import sys
my_list = []
for i in range(100):
# Смотрим, как растёт capacity
if i == 0 or (i-1 != len(my_list) - 1):
prev_size = len(my_list)
my_list.append(i)
# Capacity растёт в определённых точках:
# 0 → 1 → 5 → 9 → 13 → 17 → ...
Реальный пример: измерение времени
import time
# Append в конец (БЫСТРО)
start = time.time()
my_list = []
for i in range(1_000_000):
my_list.append(i)
print(f"Append 1M: {time.time() - start:.3f}s")
# Output: ~0.05s (очень быстро!)
# Insert в начало (МЕДЛЕННО)
start = time.time()
my_list = []
for i in range(10_000): # Только 10K для примера
my_list.insert(0, i) # Вставляет в начало
print(f"Insert 10K: {time.time() - start:.3f}s")
# Output: ~0.3-0.5s (медленнее!)
# Почему insert медленнее?
# insert(0, x) должен:
# 1. Сдвинуть ВСЕ элементы на 1 позицию вправо → O(n)
# 2. Поставить новый элемент на позицию 0
# Повторяя это 10k раз → O(n²)
Визуализация переаллокации
Шаг 1: Создаём список
my_list = []
┌─────────────┐
│ │ capacity=0, length=0
└─────────────┘
Шаг 2: Добавляем первый элемент
my_list.append(1)
┌─────┐
│ 1 │ capacity=1, length=1
└─────┘
Шаг 3: Добавляем второй (нужна переаллокация)
my_list.append(2)
┌─────────────┐
│ 1 │ 2 │ │ capacity=5, length=2
└─────────────┘
Шаг 4-5: Добавляем 3-5 (есть место)
my_list.extend([3, 4, 5])
┌─────────────┐
│ 1 │ 2 │ 3 │ 4 │ 5 │ capacity=5, length=5
└─────────────┘
Шаг 6: Добавляем 6-й элемент (нужна переаллокация!)
my_list.append(6)
┌───────────────────────────┐
│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ ? │ ? │ capacity=10, length=6
└───────────────────────────┘
Формула роста в CPython
# CPython использует эту формулу при переаллокации:
def new_capacity(old_capacity):
if old_capacity == 0:
return 1
return old_capacity + (old_capacity >> 3) + 6
# (old_capacity >> 3) это old_capacity // 8
# Примеры:
print(new_capacity(0)) # 1
print(new_capacity(1)) # 1 + 0 + 6 = 7
print(new_capacity(5)) # 5 + 0 + 6 = 11
print(new_capacity(10)) # 10 + 1 + 6 = 17
print(new_capacity(100)) # 100 + 12 + 6 = 118
Сложность операций
my_list = [1, 2, 3, 4, 5]
# O(1) — константное время
my_list.append(6) # Добавляет в конец
my_list[-1] # Доступ по индексу
my_list[2] # Доступ по индексу
# O(n) — линейное время
my_list.insert(0, 99) # Вставляет в начало (сдвигает всё)
my_list.pop(0) # Удаляет из начала (сдвигает всё)
my_list.remove(3) # Удаляет элемент (может требовать сдвига)
my_list.clear() # Очищает (но не освобождает память)
# O(n) для расширения
my_list.extend([10, 11, 12]) # Добавляет много элементов
# O(n log n) для сортировки
my_list.sort() # Timsort алгоритм
Оптимизация: pre-allocate память
# ❌ Медленно — много переаллокаций
my_list = []
for i in range(1_000_000):
my_list.append(i)
# ✅ Быстрее — выделить место заранее
my_list = [0] * 1_000_000 # Выделяем место
for i in range(1_000_000):
my_list[i] = i # Просто присваиваем
# ✅ Альтернатива
my_list = list(range(1_000_000)) # Создаём сразу
Другие типы данных
# List — динамический массив (как описано выше)
my_list = [1, 2, 3]
my_list.append(4) # Переаллокация при необходимости
# Tuple — фиксированный размер
my_tuple = (1, 2, 3) # Размер фиксирован
# my_tuple.append(4) # AttributeError — нельзя!!
# Deque — двусторонняя очередь (быстро append и appendleft)
from collections import deque
my_deque = deque([1, 2, 3])
my_deque.append(4) # O(1)
my_deque.appendleft(0) # O(1) — также быстро!
# Array — типизированный массив (для чисел)
import array
my_array = array.array('i', [1, 2, 3]) # 'i' = int
my_array.append(4) # Переаллокация
Заключение
При добавлении элемента в список:
- Если есть место — O(1), элемент добавляется мгновенно
- Если нет места — O(n), требуется переаллокация и копирование
- Амортизированная сложность — O(1) в среднем благодаря стратегии роста
- Память растёт на ~12.5% при переаллокации (в CPython)
- Append в конец быстро, но insert в начало медленно
Правило: Используй append() в конец, а не insert() в начало. Если нужны операции в обе стороны — используй deque из collections.