Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Когда сборщик мусора удаляет ссылки в Python
Звучит похоже на предыдущий вопрос, но здесь речь идёт о конкретном моменте и механизме удаления ссылок. Это важно понять для оптимизации памяти.
Два разных механизма удаления
Важно различать когда удаляется объект (его реальные данные) и когда удаляется ссылка на него.
1. Immediate Deletion — удаление по подсчёту ссылок (Reference Counting)
Это происходит сразу же, когда счётчик ссылок становится 0:
class Resource:
def __init__(self, name):
self.name = name
def __del__(self):
print(f"Resource {self.name} удалён")
obj = Resource("file.txt")
print("Объект создан")
del obj # Счётчик ссылок → 0, сразу вызывается __del__
print("После del")
# Вывод:
# Объект создан
# Resource file.txt удалён
# После del
Это самый частый способ удаления в Python.
2. Garbage Collector — удаление циклических ссылок
Если объекты образуют цикл, reference counting не сработает. GC удаляет такие объекты периодически или по команде:
class Node:
def __init__(self, value):
self.value = value
self.next = None
def __del__(self):
print(f"Node {self.value} удалён")
node1 = Node(1)
node2 = Node(2)
node1.next = node2
node2.next = node1 # Цикл!
print("Удаляем ссылки")
del node1
del node2
print("После del")
# Вывод (Python 3.13+):
# Удаляем ссылки
# Node 1 удалён
# Node 2 удалён
# После del
# (В более старых версиях __del__ может вызваться позже)
Пороги (Thresholds) сборщика мусора
Сборщик мусора НЕ работает постоянно. Он проверяет количество выделений:
import gc
# Получить пороги
thresholds = gc.get_threshold()
print(thresholds) # (700, 10, 10) — по умолчанию
# Поколение 0: запускается каждые 700 выделений объектов
# Поколение 1: запускается каждые 10 сборок поколения 0
# Поколение 2: запускается каждые 10 сборок поколения 1
Три поколения объектов
Python использует поколеченческий GC:
import gc
class TrackedObject:
def __init__(self, gen):
self.generation = gen
def __del__(self):
print(f"Объект из поколения {self.generation} удалён")
# Новые объекты в поколении 0
obj = TrackedObject(0)
print(f"Объект в поколении: {gc.get_objects().count(obj)}")
# После первой сборки → поколение 1
gc.collect()
# Старые объекты в поколении 2
# (если пережили несколько сборок)
Практический пример: контролировать моменты сборки
import gc
import time
# Отключить автоматическую сборку
gc.disable()
class Data:
def __init__(self, size):
self.data = [0] * size
def __del__(self):
print("Data удалён")
# Создавать и удалять объекты
for i in range(5):
obj = Data(1000000) # Большой объект
print(f"Создан объект {i}")
del obj # Reference counting удалит сразу
print(f"Удалён объект {i}")
print("\n=== Принудительная сборка ===")
released = gc.collect()
print(f"Собрано объектов: {released}")
gc.enable()
Когда именно происходит удаление ссылок
1. При присваивании (overwrite)
obj1 = [1, 2, 3] # Создана ссылка
obj1 = [4, 5, 6] # Старая ссылка удалена, новая создана
# Старый список удалён (если на него нет других ссылок)
2. При выходе из области видимости
def func():
obj = Resource() # Ссылка создана
# ...
return result
# obj удалён здесь (выход из видимости)
func()
3. При явном удалении
del obj # Ссылка удалена явно
4. При сборке мусора
gc.collect() # Удаляются циклические ссылки
Полный процесс жизненного цикла ссылки
1. СОЗДАНИЕ
obj = SomeClass() # Ссылка создана, счётчик = 1
2. СУЩЕСТВОВАНИЕ
another_ref = obj # Счётчик = 2
return obj # Передана в функцию
3. УДАЛЕНИЕ ССЫЛКИ
del another_ref # Счётчик = 1
# Функция вернула управление, счётчик = 0
4. УДАЛЕНИЕ ОБЪЕКТА
# Когда счётчик = 0:
# 1. Вызывается __del__() объекта
# 2. Память освобождается
Детальный пример с подсчётом ссылок
import sys
class MyObject:
pass
obj = MyObject()
print(f"После создания: {sys.getrefcount(obj) - 1} ссылок")
# -1 потому что getrefcount сам создаёт ссылку
ref1 = obj
print(f"После ref1 = obj: {sys.getrefcount(obj) - 1} ссылок") # 2
ref2 = obj
print(f"После ref2 = obj: {sys.getrefcount(obj) - 1} ссылок") # 3
del ref1
print(f"После del ref1: {sys.getrefcount(obj) - 1} ссылок") # 2
del ref2
print(f"После del ref2: {sys.getrefcount(obj) - 1} ссылок") # 1
del obj
print("obj удалён")
Специальный случай: слабые ссылки (weakref)
Для избежания циклических ссылок:
import weakref
class Parent:
def __init__(self):
self.children = []
class Child:
def __init__(self, parent):
self.parent = weakref.ref(parent) # Слабая ссылка!
parent.children.append(self)
parent = Parent()
child = Child(parent)
print(f"Ссылок на parent: {sys.getrefcount(parent) - 1}") # Не увеличилась!
del parent # Удалится нормально, без цикла
Порядок удаления при выходе из программы
class ShutdownTest:
def __init__(self, name):
self.name = name
def __del__(self):
print(f"{self.name} удалён")
obj1 = ShutdownTest("first")
obj2 = ShutdownTest("second")
obj3 = ShutdownTest("third")
# Вывод при выходе:
# third удалён
# second удалён
# first удалён
# (В обратном порядке создания!)
Вывод
Сборщик мусора удаляет ссылки в три момента:
-
Немедленно (Reference Counting) — когда счётчик ссылок = 0
- Это основной механизм
- Происходит при
delили выходе из видимости - Самое быстрое решение
-
Периодически (Garbage Collector) — по достижении порогов
- Удаляет циклические ссылки
- По умолчанию: каждые 700 выделений (поколение 0)
- Можно отключить или настроить
-
Явно — при вызове
gc.collect()- Немедленная сборка
- Используется для отладки и оптимизации
В большинстве случаев (95%+) объекты удаляются сразу по reference counting, не дожидаясь GC.