Деструктор в Python: уничтожение объектов

Деструктор в Python – это специальный метод, который вызывается при уничтожении объекта. Конструктор же, наоборот, используется для создания и инициализации объекта класса.

В этой статье мы разберем:

  • как создать деструктор в Python
  • использование метода __del__()
  • как работает деструктор.

Что такое деструктор в Python?

В объектно-ориентированном программировании деструктор вызывается при удалении или уничтожении объекта. Деструктор используется для выполнения действий по очистке перед разрушением объекта, таких как закрытие соединений с базой данных или дескриптор файла.

В Python есть сборщик мусора, который автоматически обрабатывает управление памятью. Например, он очищает память, когда объект выходит за пределы области видимости.

Однако при уничтожении объекта необходимо освобождать не только память. Мы должны освободить или закрыть другие ресурсы, которые использовались объектом, такие как открытые файлы, соединения с базой данных, очистка буфера или кеша. Для выполнения всех этих задач очистки мы используем деструктор в Python.

Деструктор противоположен конструктору. Конструктор используется для инициализации объектов, а деструктор — для удаления или уничтожения объектов, в результате чего освобождается ресурс, занятый этими объектами.

В Python деструктор вызывается не вручную, а полностью автоматически. Это происходит в следующих двух случаях:

  • когда объект выходит за пределы области видимости
  • когда счетчик ссылок на объект достигает 0.

Для определения деструктора используется специальный метод __del__(). Например, когда мы выполняем del имя_объекта, деструктор вызывается автоматически, и объект собирается в мусор.

Деструктор в Python для уничтожения объектов

Создание деструктора с помощью метода __del__()

Магический метод __del__() используется как деструктор в Python. Метод __del__() будет неявно вызываться, когда все ссылки на объект будут удалены, то есть когда объект подходит для сборщика мусора.

Этот метод автоматически вызывается в Python, когда экземпляр собираются уничтожить. Его также называют финализатором или (неправильно) деструктором.

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

def __del__(self):
    # тело деструктора

Здесь:

  • def – ключевое слово, которое используется для определения метода.
  • __del__() – зарезервированный метод. Он вызывается, как только все ссылки на объект будут удалены.
  • self: первый аргумент self относится к текущему объекту.

Примечание. Аргументы метода __del__() необязательны. Мы можем определить деструктор с любым количеством аргументов.

Пример

Давайте рассмотрим создание деструктора в Python на простом примере. Мы создадим класс Student с деструктором.

class Student:

    # конструктор
    def __init__(self, name):
        print('Inside Constructor')
        self.name = name
        print('Object initialized')

    def show(self):
        print('Hello, my name is', self.name)

    # деструктор
    def __del__(self):
        print('Inside destructor')
        print('Object destroyed')

# создать объект
s1 = Student('Emma')
s1.show()

# удалить объект
del s1

Запустим наш код и получим следующий результат:

Inside Constructor
Object initialized

Hello, my name is Emma

Inside destructor
Object destroyed

Примечание. Как видно из вывода, при удалении ссылки на объект с помощью del s1 метод __del__() вызывается автоматически.

В приведенном выше коде мы создали один объект. s1 – это ссылочная переменная, указывающая на вновь созданный объект.

Деструктор вызывается, когда ссылка на объект удалена или счетчик ссылок на объект доходит до нуля.

[python_ad_block]

Важные моменты, которые следует помнить о деструкторе

  • Метод __del__() вызывается для любого объекта, когда счетчик ссылок для этого объекта становится равным нулю.
  • Счетчик ссылок для данного объекта становится нулевым, когда работа программы завершается, или мы удаляем все ссылки вручную с помощью ключевого слова del.
  • Деструктор не будет запускаться при удалении какой-то одной ссылки на объект. Он будет вызываться только тогда, когда все ссылки на объект будут удалены.
Работа деструктора

Пример

Давайте разберемся в приведенных выше пунктах на примере.

Сначала создадим объект класса Student, используя s1 = student ('Emma').

Затем давайте создадим новую ссылку на объект, присвоив переменной s2 значение s1 (т.е. s2 = s1).

Теперь обе ссылочные переменные s1 и s2 указывают на один и тот же объект.


От редакции Pythonist. Ссылки, имена и значения подробно рассмотрены в статье «Факты и мифы об именах и значениях в Python».


Далее мы удалим ссылку s1.

Затем добавим 5 секунд задержки (sleep) к основному потоку, чтобы было ясно, что деструкторы вызываются только при удалении всех ссылок на объекты.

import time

class Student:

    # конструктор
    def __init__(self, name):
        print('Inside Constructor')
        self.name = name

    def show(self):
        print('Hello, my name is', self.name)

    # деструктор
    def __del__(self):
        print('Object destroyed')

# создание объекта
s1 = Student('Emma')
# создание новой ссылки
# обе ссылки указывают на один объект
s2 = s1
s1.show()

# удаление ссылки s1
del s1

# добавление задержки и наблюдение за результатом
time.sleep(5)
print('After sleep')
s2.show()

Наш вывод:

Inside Constructor
Hello, my name is Emma

После паузы:

After sleep
Hello, my name is Emma
Object destroyed
  • Как вы можете видеть из полученного результата, деструкторы вызываются только тогда, когда удаляются все ссылки на объекты.
  • Кроме того, деструктор выполняется, когда код (программа) заканчивается и объект становится доступным для сборщика мусора. Например, мы не удаляли ссылку на объект s2 вручную с помощью del s2. Это произошло автоматически, т.к. программа закончилась.

Случаи, когда деструктор работает не корректно

__del__() не является идеальным решением для очистки ненужных объектов. В Python деструктор ведет себя странно и не выполняется в следующих двух случаях:

  • ссылка является круговой: два объекта ссылаются друг на друга
  • исключение в методе __init__()

Круговая ссылка

__del__() некорректно работает в случае циклической ссылки.Суть этой ссылки в том, что два объекта ссылаются друг на друга. И когда оба объекта выходят за пределы области видимости, Python не знает, какой объект уничтожить первым. Поэтому, чтобы избежать ошибок, он не уничтожает ни один из них.

Короче говоря, это означает, что сборщик мусора не знает порядок, в котором объекты должны быть уничтожены, поэтому он не удаляет их из памяти.

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

Пример

В приведенном ниже примере в идеале оба объекта — Vehicle и Car — должны быть уничтожены сборщиком мусора после выхода за пределы области видимости. Тем не менее, из-за циклической ссылки они остаются в памяти.

Для управления ресурсами, которые необходимо очистить, можно посоветовать использовать оператор with.

import time


class Vehicle():
    def __init__(self, id, car):
        self.id = id;
        # сохранение ссылки объекта Car
        self.dealer = car;
        print('Vehicle', self.id, 'created');

    def __del__(self):
        print('Vehicle', self.id, 'destroyed');


class Car():
    def __init__(self, id):
        self.id = id;
        # сохранение объекта класса Vehicle в переменной 'dealer'
        # пересылка ссылки объекта Car ('self') для объекта Vehicle
        self.dealer = Vehicle(id, self);
        print('Car', self.id, 'created')

    def __del__(self):
        print('Car', self.id, 'destroyed')


# создание объекта car
c = Car(12)
# удаление объекта car
del c
# в идеале теперь должен выполниться деструктор

# задержка для наблюдения за поведением
time.sleep(8)

Запустим наш код и получим следующее:

Vehicle 12 created
Car 12 created

Исключение в методе __init__()

В объектно-ориентированном программировании конструктор – это специальный метод, используемый для создания и инициализации объекта класса. Используя метод __init__(), мы можем реализовать конструктор для инициализации объекта.

В ООП, если в конструкторе возникает какое-либо исключение при инициализации объекта, конструктор уничтожает объект.

Аналогично, в Python, если в методе инициализации возникает какое-либо исключение при инициализации объекта, вызывается метод del. Но на самом деле объект не создается, и ему не выделяются ресурсы.

Несмотря на то, что объект так и не был инициализирован правильно, метод del все равно попытается очистить все ресурсы. А это, в свою очередь, может привести к другому исключению.

Пример

class Vehicle:
    def __init__(self, speed):
        if speed > 240:
            raise Exception('Not Allowed');
        self.speed = speed;

    def __del__(self):
        print('Release resources')

# создание объекта
car = Vehicle(350);
# для явного удаления объекта:
del car

Запустив этот код, мы получим следующий результат:

Traceback (most recent call last):
Release resources
Exception: Not Allowed

Заключение

В объектно-ориентированном программировании деструктор вызывается при удалении или уничтожении объекта.

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

Для выполнения задачи очистки перед удалением объекта в Python мы используем метод __del__().

При удалении ссылки на объект деструктор не запускается. Для этого нужно удалить все ссылки на этот объект.

Перевод статьи «Python Destructors to Destroy the Object».