Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как устроены изменяемые объекты в памяти?
Изменяемые объекты (mutable objects) — это объекты, которые можно изменять после создания (списки, словари, множества, пользовательские классы). Понимание их устройства в памяти критично для избежания ошибок и оптимизации кода.
Базовые понятия
В Python всё — это объект с:
- id — уникальный идентификатор в памяти (адрес в CPython)
- type — тип объекта
- value — значение (содержимое)
x = [1, 2, 3]
print(id(x)) # 140734567890000 — адрес объекта
print(type(x)) # <class 'list'> — тип
print(x) # [1, 2, 3] — значение
Как список хранится в памяти
Список — это динамический массив указателей:
my_list = [10, 20, 30]
# В памяти структура выглядит так:
# my_list (переменная)
# ↓
# PyListObject (объект списка)
# ├─ ob_size = 3 (количество элементов)
# ├─ ob_capacity = 4 (выделенная память)
# └─ ob_item → массив указателей
# ├─ → PyObject(10)
# ├─ → PyObject(20)
# └─ → PyObject(30)
Демонстрация с id()
# id() показывает адрес объекта
my_list = [1, 2, 3]
original_id = id(my_list)
my_list.append(4) # Изменяем список
print(id(my_list) == original_id) # True! — объект в памяти тот же
my_list = [1, 2, 3, 4] # Переприсваиваем переменную
print(id(my_list) == original_id) # False — новый объект
Ссылки на мутабельные объекты
Важно понимать, что переменная — это только ссылка на объект:
a = [1, 2, 3]
b = a # b указывает на ТОТ ЖЕ объект, что и a
print(id(a) == id(b)) # True
a.append(4)
print(b) # [1, 2, 3, 4] — b изменилась, потому что указывает на тот же объект!
# Это опасно при копировании:
original = [1, 2, 3]
copy = original # Не копирование, а ещё одна ссылка!
copy[0] = 999
print(original) # [999, 2, 3] — original тоже изменилась!
Правильное копирование
# Поверхностное копирование (shallow copy)
import copy
original = [1, 2, 3]
copy_shallow = copy.copy(original)
# или
copy_shallow = original.copy()
copy_shallow[0] = 999
print(original) # [1, 2, 3] — original не изменилась
# Но для вложенных структур может быть проблема:
original = [[1, 2], [3, 4]]
copy_shallow = original.copy()
copy_shallow[0][0] = 999
print(original) # [[999, 2], [3, 4]] — внутренние списки всё ещё общие!
# Глубокое копирование (deep copy)
copy_deep = copy.deepcopy(original)
copy_deep[0][0] = 999
print(original) # [[999, 2], [3, 4]] — original не изменилась
Как память распределяется для списка
my_list = []
print(len(my_list), sys.getsizeof(my_list)) # 0, 56 байт (пустой список)
# Python часто выделяет больше памяти, чем нужно
for i in range(10):
my_list.append(i)
print(f"len={len(my_list)}, size={sys.getsizeof(my_list)} bytes")
# Выход примерно:
# len=1, size=88 bytes (выделено под 4 элемента)
# len=2, size=88 bytes
# len=3, size=88 bytes
# len=4, size=88 bytes
# len=5, size=120 bytes (выделено под 8 элементов)
# len=10, size=120 bytes
Словарь в памяти
Словарь использует хеш-таблицу для быстрого поиска O(1):
my_dict = {'a': 1, 'b': 2, 'c': 3}
# В памяти (упрощённо):
# PyDictObject
# ├─ hash('a') % size → [KeyObject('a'), ValueObject(1)]
# ├─ hash('b') % size → [KeyObject('b'), ValueObject(2)]
# └─ hash('c') % size → [KeyObject('c'), ValueObject(3)]
Множество (set)
Множество похоже на словарь, но содержит только ключи (без значений):
my_set = {1, 2, 3}
# В памяти:
# PySetObject → хеш-таблица
# ├─ hash(1) % size → 1
# ├─ hash(2) % size → 2
# └─ hash(3) % size → 3
Пользовательский класс
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(10, 20)
p2 = p1 # Ссылка на тот же объект
print(id(p1) == id(p2)) # True
p1.x = 100
print(p2.x) # 100 — p2 тоже изменилась!
# В памяти Point содержит __dict__:
print(p1.__dict__) # {'x': 100, 'y': 20}
print(id(p1.__dict__) == id(p2.__dict__)) # True
Практическая проблема: изменяемый аргумент по умолчанию
# ❌ Опасно!
def add_item(item, items=[]):
items.append(item)
return items
result1 = add_item(1) # [1]
result2 = add_item(2) # [1, 2] — ПРОБЛЕМА!
result3 = add_item(3) # [1, 2, 3] — список переиспользуется!
print(id(result1) == id(result2) == id(result3)) # True — один и тот же объект!
# ✅ Правильно!
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
result1 = add_item(1) # [1]
result2 = add_item(2) # [2] — новый список
result3 = add_item(3) # [3] — новый список
Оптимизация памяти
import sys
# Целые числа кешируются
a = 256
b = 256
print(id(a) == id(b)) # True (CPython кеширует числа -5..256)
# Но не всегда
a = 257
b = 257
print(id(a) == id(b)) # Может быть False (зависит от контекста)
# Строки также кешируются
a = "hello"
b = "hello"
print(id(a) == id(b)) # True (интернирование строк)
# Списки никогда не переиспользуются
a = []
b = []
print(id(a) == id(b)) # False — разные объекты
Резюме
- Переменные — это ссылки, а не значения
- Изменение мутабельного объекта видно через все переменные, указывающие на него
- Копирование требует явного вызова
copy()илиcopy.deepcopy() - Стандартные типы (список, словарь) оптимизированы для скорости, но требуют внимательного использования
- id() помогает отладить проблемы с памятью