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

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

1.8 Middle🔥 131 комментариев
#Python Core

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

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

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

Ссылочная модель памяти в Python

В Python всё, что угодно — целые числа, строки, функции, классы — это объекты в памяти. Переменные не хранят сами значения, а содержат ссылки (references) на объекты. Это фундаментальное различие от языков типа C.

Объекты и ссылки

Базовая концепция

# Это не присваивание значения, а создание ссылки на объект
x = [1, 2, 3]  # x указывает на список
y = x          # y указывает на ТОТ ЖЕ список

print(x is y)  # True — одинаковые объекты в памяти
print(id(x) == id(y))  # True — одинаковые адреса

# Изменение через одну ссылку видно в другой
y.append(4)
print(x)  # [1, 2, 3, 4] — изменилось!

Важное различие: изменяемые vs неизменяемые объекты

# Неизменяемые (immutable): int, str, tuple, frozenset
a = 10
b = a
b = 20  # Это создаёт НОВЫЙ объект, не меняет существующий

print(a)  # 10 — a не изменилась
print(id(a) != id(b))  # True — разные объекты

# Изменяемые (mutable): list, dict, set
x = [1, 2]
y = x
y[0] = 99  # Меняет существующий объект

print(x)  # [99, 2] — изменилось через x!
print(id(x) == id(y))  # True — всё ещё одинаковые объекты

Глубокое копирование vs поверхностное

import copy

# Поверхностное копирование (shallow copy)
original = [1, [2, 3], 4]
shallow = original.copy()  # или list(original)

shallow[0] = 99
print(original)  # [1, [2, 3], 4] — первый уровень не затронут

shallow[1][0] = 99
print(original)  # [1, [99, 3], 4] — вложенные объекты МЕНЯЮТСЯ!

# Глубокое копирование (deep copy)
original = [1, [2, 3], 4]
deep = copy.deepcopy(original)

deep[1][0] = 99
print(original)  # [1, [2, 3], 4] — НИЧЕГО не изменилось
print(deep)      # [1, [99, 3], 4]

Счётчик ссылок (Reference Counting)

import sys

# sys.getrefcount() показывает количество ссылок на объект
x = [1, 2, 3]
print(sys.getrefcount(x))  # Обычно 2 (сама переменная + аргумент функции)

y = x
print(sys.getrefcount(x))  # Теперь 3 (x, y, и аргумент функции)

del y
print(sys.getrefcount(x))  # Вернулось к 2

Garbage Collection

Когда счётчик ссылок объекта становится нулём, Python автоматически удаляет объект:

class MyClass:
    def __del__(self):
        print("Объект удалён из памяти")

x = MyClass()
print("x создана")  # Объект существует

del x  # Удаляем ссылку
# Вывод: "Объект удалён из памяти"

# Циклические ссылки требуют помощи
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

a = Node(1)
b = Node(2)
a.next = b
b.next = a  # Циклическая ссылка!

# Без явного удаления, garbage collector обнаружит и удалит
del a, b

Параметры функций (pass by object reference)

def modify_list(lst):
    lst.append(999)  # Меняет исходный список

def reassign_list(lst):
    lst = [7, 8, 9]  # Создаёт новый локальный объект

my_list = [1, 2, 3]
modify_list(my_list)
print(my_list)  # [1, 2, 3, 999] — изменилось

my_list = [1, 2, 3]
reassign_list(my_list)
print(my_list)  # [1, 2, 3] — НЕ изменилось

Область видимости (Scope)

x = 10  # Глобальная переменная

def func():
    x = 20  # Локальная переменная (новый объект)
    print(x)  # 20

func()
print(x)  # 10 — глобальная не изменилась

# Изменение глобальной переменной
def modify_global():
    global x
    x = 30

modify_global()
print(x)  # 30

# Нелокальные переменные (nonlocal) в замыканиях
def outer():
    x = 10
    
    def inner():
        nonlocal x  # Ссылка на x из outer()
        x = 20
    
    inner()
    return x

print(outer())  # 20

Ссылки и контейнеры

# Список содержит ссылки на объекты, не сами объекты
obj = {"key": "value"}
my_list = [obj, obj, obj]  # Три ссылки на ОДИН словарь

obj["key"] = "changed"
print(my_list[0]["key"])  # "changed" — все три элемента изменились

# Словарь может содержать себя
my_dict = {"a": 1}
my_dict["self"] = my_dict  # Циклическая ссылка

Сравнение == и is

# == сравнивает ЗНАЧЕНИЯ
# is сравнивает ИДЕНТИЧНОСТЬ (тот же объект в памяти)

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)   # True — одинаковые значения
print(a is b)   # False — разные объекты
print(id(a) != id(b))  # True

# Исключение: малые целые числа и строки кешируются
c = 256
d = 256
print(c is d)  # True (Python кеширует числа от -5 до 256)

e = 257
f = 257
print(e is f)  # False (обычно, в разных условиях)

Практический пример: неправильное использование значений по умолчанию

# ОШИБКА: изменяемый объект как значение по умолчанию
def add_item(item, container=[]):
    container.append(item)  # Один список на всех!
    return container

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2] — ошибка!
print(add_item(3))  # [1, 2, 3] — ошибка!

# ПРАВИЛЬНО: None как значение по умолчанию
def add_item_correct(item, container=None):
    if container is None:
        container = []
    container.append(item)
    return container

print(add_item_correct(1))  # [1]
print(add_item_correct(2))  # [2]

Память и производительность

import sys

# Размер объекта в памяти
print(sys.getsizeof(42))      # 28 bytes (для int)
print(sys.getsizeof("hello")) # 54 bytes (для str)
print(sys.getsizeof([1, 2, 3]))  # 56 bytes (для list)

# Ссылка занимает 8 байт на 64-битной системе
# Но внутренние структуры требуют памяти для счётчика ссылок, типа и т.д.

Лучшие практики

  • Помни про изменяемые параметры — передавай копии если нужно
  • Используй copy.deepcopy() для глубокого копирования
  • Проверяй с is только None, True, False — для остального используй ==
  • Будь осторожен с изменяемыми значениями по умолчанию
  • Понимай разницу между modify и reassign в функциях
  • Разбирайся в циклических ссылках если работаешь со сложными структурами

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