Как 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