Как Python хранит переменные и управляет памятью

Раздел: Основы Python -> Управление памятью

Основы работы с памятью переменных в Python

Как Python представляет переменную в памяти и почему это важно понимать?

В Python переменная не является контейнером для значения. Она представляет собой ссылку (имя) на объект в памяти. Каждый объект имеет уникальный идентификатор (возвращаемый функцией id()), тип и значение. Когда переменной присваивается значение, создаётся объект, а переменная связывается с ним. При повторном присвоении другой переменной того же значения может использоваться уже существующий объект (например, для малых целых чисел и коротких строк применяется интернирование).

a = 256      # объект int создаётся один раз (интернирование)
b = 256      # b ссылается на тот же объект
print(a is b)  # True, потому что id одинаковы

c = 257
d = 257
print(c is d)  # False, так как большие числа не интернируются

идентификаторах объектов python (идентификаторы объектов в python)

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

Почему изменение списка через одну переменную меняет другую?

list1 = [1, 2, 3]
list2 = list1        # копируется только ссылка
list2.append(4)
print(list1)         # [1, 2, 3, 4] – ошибка ожидания

Python память переменная (память переменной в python)

Проблема решается созданием копии (поверхностной или глубокой) вместо простого присваивания.

Какие бывают варианты копирования объектов?

В Python существует три основных подхода к созданию копий: поверхностное копирование (shallow copy), глубокое копирование (deep copy) и копирование срезом для последовательностей. Выбор зависит от структуры данных и требуемой степени независимости.

Как сделать поверхностную копию списка или словаря?

original = [[1, 2], [3, 4]]
shallow = list(original)        # или original.copy()
shallow[0].append(99)
print(original[0])              # [1, 2, 99] – вложенный объект разделяется

управление памяти python (управление памятью в python)

Почему изменение внутри вложенного списка затрагивает оригинал?

Поверхностная копия создаёт новый внешний контейнер, но вложенные объекты остаются общими. Для полной независимости требуется глубокая копия.

Как создать полностью независимую копию произвольного объекта?

import copy

original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(99)
print(original[0])              # [1, 2] – не изменилось

Глубокая копия рекурсивно копирует все вложенные объекты, что может быть ресурсоёмко для больших структур.

Как сборщик мусора освобождает память?

Python использует автоматический подсчёт ссылок и циклический сборщик мусора. Когда количество ссылок на объект становится нулевым, память немедленно освобождается. Для циклов ссылок (например, два объекта ссылаются друг на друга) применяется циклический детектор.

import gc
gc.collect()                    # принудительный сбор мусора
print(gc.get_count())           # текущее состояние счётчиков

Почему программа может потреблять много памяти даже после удаления переменных?

Возможные причины: циклические ссылки, глобальные кэши, объекты в контейнерах (например, список, который не очищен). Решение: явно обнулять ссылки, использовать del и вызывать gc.collect() при необходимости.

Влияет ли тип данных на управление памятью?

Неизменяемые типы (int, str, tuple) часто интернируются и могут разделять память. Изменяемые типы (list, dict, set) всегда создают новые объекты при каждом изменении. Однако присваивание одной переменной другой всегда копирует ссылку, независимо от типа.

x = (1, 2, 3)
y = x             # ссылка на тот же кортеж (неизменяемый – безопасно)
z = (1, 2, 3)
print(x is z)     # может быть True из-за интернирования кортежей

Знание этих различий помогает избежать неожиданного поведения при передаче аргументов в функции.

Расширенные примеры управления памятью

1. Просмотр идентификаторов и ссылок

Пример
a = [1, 2, 3]
b = a
c = a[:]          # поверхностная копия срезом
print(id(a), id(b), id(c))   # a и b одинаковы, c разный
b.append(4)
print(a)          # [1, 2, 3, 4]
c.append(5)
print(a)          # [1, 2, 3, 4] – c независим
1398123456780 1398123456780 1398123461200
[1, 2, 3, 4]
[1, 2, 3, 4]

2. Глубокое копирование с пользовательскими классами

Пример
class MyClass:
    def __init__(self, data):
        self.data = data

import copy
obj = MyClass([1, [2, 3]])
shallow = copy.copy(obj)
deep = copy.deepcopy(obj)

shallow.data[1].append(4)
print(obj.data)        # [1, [2, 3, 4]]

deep.data[1].append(5)
print(obj.data)        # [1, [2, 3, 4]] – глубокое копирование защищает
[1, [2, 3, 4]]
[1, [2, 3, 4]]

3. Утечка памяти из-за циклических ссылок

Пример
import gc
gc.disable()          # отключаем сборщик циклических ссылок

class Node:
    def __init__(self, val):
        self.val = val
        self.ref = None

node1 = Node(1)
node2 = Node(2)
node1.ref = node2
node2.ref = node1

del node1
del node2
# Счётчик ссылок обоих объектов >0 из-за цикла, память не освобождена
print(gc.collect())   # возвращает количество собранных объектов (2)
2

4. Интернирование строк и чисел

Пример
import sys

s1 = "hello"
s2 = "hello"
print(s1 is s2)       # True (интернировано)

# Длинная строка может не интернироваться
s3 = "hello" * 1000
s4 = "hello" * 1000
print(s3 is s4)       # False, но зависит от реализации

# Малые целые от -5 до 256 интернируются
print(256 is 256)     # True
print(257 is 257)     # False (обычно)
True
False
True
False

5. Использование weakref для избежания утечек

Пример
import weakref

class BigObject:
    pass

obj = BigObject()
r = weakref.ref(obj)
print(r() is obj)     # True, объект существует

del obj
print(r() is None)    # True, объект удалён
True
True

6. Кэширование с помощью functools.lru_cache

Пример
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(50))         # вычисляется с кэшем, память под результаты
12586269025

Память переменной в Python - comments

En
Python память переменная (python)