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

В каком порядке удаляются объекты в Python

1.7 Middle🔥 71 комментариев
#Python Core

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

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

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

# Порядок удаления объектов в Python

Это важный вопрос о garbage collection и управлении памятью в Python. Порядок удаления объектов зависит от нескольких факторов.

Основной механизм: Reference Counting

Python использует reference counting как основной механизм управления памятью. Объект удаляется, когда его счетчик ссылок (reference count) становится 0.

import sys

class MyObject:
    def __init__(self, name):
        self.name = name
    
    def __del__(self):
        print(f"{self.name} удален")

# Создаем объект
obj = MyObject("obj1")  # ref_count = 1
print(f"Ref count: {sys.getrefcount(obj)}")  # 2 (из-за аргумента getrefcount)

# Второе присваивание
obj2 = obj  # ref_count = 2
print(f"Ref count: {sys.getrefcount(obj)}")  # 3

# Удаляем первую ссылку
del obj  # ref_count = 1, объект НЕ удален
print("obj удален (переменная)")
# Вывод: (ничего)

# Удаляем вторую ссылку
del obj2  # ref_count = 0, объект УДАЛЕН
print("obj2 удален (переменная)")
# Вывод:
# obj1 удален

Порядок удаления объектов

1 Когда выходит из scope

def example():
    obj1 = MyObject("obj1")  # создан
    obj2 = MyObject("obj2")  # создан
    
    if True:
        obj3 = MyObject("obj3")  # создан
        obj4 = MyObject("obj4")  # создан
    
    # obj3 и obj4 удалены здесь (выходят из scope блока)
    # Вывод:
    # obj4 удален
    # obj3 удален
    
    # или может быть в другом порядке (не гарантировано)

# Конец функции
# obj1 и obj2 удалены
# Вывод:
# obj2 удален
# obj1 удален

example()

2 В обратном порядке создания (часто, но не всегда)

obj_a = MyObject("A")  # создан
obj_b = MyObject("B")  # создан
obj_c = MyObject("C")  # создан

# Удаляем все
del obj_a
del obj_b
del obj_c

# Возможный порядок удаления:
# C удален
# B удален
# A удален

3 Циклические ссылки (проблема)

class Node:
    def __init__(self, name):
        self.name = name
        self.next = None
    
    def __del__(self):
        print(f"{self.name} удален")

# Циклические ссылки
node_a = Node("A")
node_b = Node("B")

node_a.next = node_b
node_b.next = node_a  # ЦИКЛИЧЕСКАЯ ССЫЛКА!

print("Удаляем node_a и node_b")
del node_a
del node_b

print("Конец программы")
# Вывод:
# Удаляем node_a и node_b
# Конец программы
# (ОБЪЕКТЫ НЕ УДАЛЕНЫ! Только при выходе из программы через GC)

Решение: использовать weak references

import weakref

class Node:
    def __init__(self, name):
        self.name = name
        self.next = None
    
    def __del__(self):
        print(f"{self.name} удален")

node_a = Node("A")
node_b = Node("B")

node_a.next = node_b
node_b.next = weakref.ref(node_a)  # Слабая ссылка

print("Удаляем nodes")
del node_a
del node_b

print("Конец программы")
# Вывод:
# Удаляем nodes
# B удален
# A удален

Garbage Collection для циклов

Есть отдельный механизм для циклических ссылок — generational garbage collector.

import gc

class Node:
    def __init__(self, name):
        self.name = name
        self.next = None
    
    def __del__(self):
        print(f"{self.name} удален")

# Явно вызовем GC
gc.collect()
print("GC собран")

# Создаем циклические ссылки
node_a = Node("A")
node_b = Node("B")
node_a.next = node_b
node_b.next = node_a

del node_a
del node_b
print("Удалены переменные")

# Явно вызовем GC
gc.collect()
print("GC собран")

# Вывод:
# GC собран
# Удалены переменные
# B удален
# A удален
# GC собран

Контроль порядка удаления

Способ 1: Явное удаление

obj1 = MyObject("1")
obj2 = MyObject("2")
obj3 = MyObject("3")

# Удаляем в определённом порядке
del obj1  # 1 удален
del obj3  # 3 удален
del obj2  # 2 удален

# Вывод:
# 1 удален
# 3 удален
# 2 удален

Способ 2: Context Manager (with statement)

class Resource:
    def __init__(self, name):
        self.name = name
        print(f"{self.name} создан")
    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"{self.name} закрыт")
        return False
    
    def __del__(self):
        print(f"{self.name} удален (GC)")

# Context manager гарантирует порядок очистки
with Resource("res1") as r1:
    with Resource("res2") as r2:
        print("Работаем с ресурсами")
    print("res2 закрыт")
print("res1 закрыт")

# Вывод:
# res1 создан
# res2 создан
# Работаем с ресурсами
# res2 закрыт
# res1 закрыт

Способ 3: Destruct в обратном порядке создания (Python).

def cleanup_order():
    objs = []
    for i in range(3):
        objs.append(MyObject(f"obj{i}"))
    
    # Удаляем в обратном порядке
    for obj in reversed(objs):
        del obj

cleanup_order()
# Вывод:
# obj2 удален
# obj1 удален
# obj0 удален

Практический пример: Database Connection

class DatabaseConnection:
    def __init__(self, host):
        self.host = host
        self.connected = True
        print(f"Connected to {host}")
    
    def close(self):
        self.connected = False
        print(f"Disconnected from {self.host}")
    
    def __del__(self):
        if self.connected:
            self.close()

# ПЛОХО: полагаться на __del__
def bad_approach():
    db = DatabaseConnection("localhost")  # Connected
    # Забыли закрыть!
    # Будет закрыто только при GC (неопределённо когда)

# ХОРОШО: использовать context manager
class DatabaseConnection:
    def __init__(self, host):
        self.host = host
        self.connected = True
        print(f"Connected to {host}")
    
    def close(self):
        self.connected = False
        print(f"Disconnected from {self.host}")
    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
        return False

def good_approach():
    with DatabaseConnection("localhost") as db:  # Connected
        # Работаем с БД
        pass
    # Disconnected (гарантировано)

good_approach()

Порядок удаления в list/dict

my_list = [MyObject("a"), MyObject("b"), MyObject("c")]

# a создан
# b создан  
# c создан

print("Удаляем список")
del my_list

# Вывод (порядок может быть разным):
# c удален
# b удален
# a удален

# или
# a удален
# b удален
# c удален

Инспекция reference count

import sys
import gc

obj = MyObject("test")

print(f"Reference count: {sys.getrefcount(obj)}")  # 2

# Получить ВСЕ ссылки
referrers = gc.get_referrers(obj)
print(f"Referrers: {len(referrers)}")

for ref in referrers:
    print(type(ref))
    # <class 'list'> (из locals())
    # <class 'list'> (из get_referrers)

Выводы

Порядок удаления объектов в Python:

  1. Reference Counting — основной механизм

    • Объект удаляется, когда ref_count == 0
    • Обычно в обратном порядке создания (но не гарантировано)
  2. Циклические ссылки — проблема

    • Требуют явного вызова gc.collect()
    • Используй weakref для предотвращения
  3. Context Managers — лучшая практика

    • Гарантируют порядок и время очистки
    • Используй with statement для ресурсов
  4. Не полагайся на del

    • Неопределённое время вызова
    • Может быть вообще не вызван
    • Используй context managers вместо этого
  5. Best Practice:

# Правильно
with resource() as r:
    # работа
    pass
# Гарантировано очищено

# Неправильно  
resource = create_resource()
# полагаться на __del__ опасно