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

Когда сборщик мусора удаляет ссылки?

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

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

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

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

# Когда сборщик мусора удаляет ссылки в 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 удалён
# (В обратном порядке создания!)

Вывод

Сборщик мусора удаляет ссылки в три момента:

  1. Немедленно (Reference Counting) — когда счётчик ссылок = 0

    • Это основной механизм
    • Происходит при del или выходе из видимости
    • Самое быстрое решение
  2. Периодически (Garbage Collector) — по достижении порогов

    • Удаляет циклические ссылки
    • По умолчанию: каждые 700 выделений (поколение 0)
    • Можно отключить или настроить
  3. Явно — при вызове gc.collect()

    • Немедленная сборка
    • Используется для отладки и оптимизации

В большинстве случаев (95%+) объекты удаляются сразу по reference counting, не дожидаясь GC.

Когда сборщик мусора удаляет ссылки? | PrepBro