Методы класса, конструктор и деструктор

На предыдущем занятии мы с вами разобрались с понятием класса в Python, его атрибутами и посмотрели как создаются экземпляры класса. Теперь ответим на вопрос: что же может содержать класс? По сути, только две вещи: атрибуты (свойства) и методы. Атрибуты – это переменные класса, а методы – это функции, реализующие определенную логику (то есть, функциональность) класса. Далее, мы будем использовать термин метод, подразумевая под ним функцию класса. Имена методов, обычно, являются глаголами, т.к. выполняют определенные действия. В то время, как имена атрибутов – это существительные, т.к. лишь хранят определенные данные.

Давайте, для примера объявим метод setCoords в классе Point:

class Point:
    x = 1; y = 1
    def setCoords(self):
        pass

Здесь сразу бросается в глаза вот этот параметр self. Зачем он здесь, если мы пока ничего не собираемся передавать этому методу? Так требуется делать по синтаксису самого Питона: когда мы объявляем какой-либо метод внутри класса, то обязательно должны указывать такой первый параметр. Теперь нужно разобраться: что он означает? При вызове этого метода, например, через экземпляр pt:

pt = Point()
pt.setCoords()

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

Давайте в этом убедимся и выведем список локальных атрибутов экземпляра, на который ссылается self:

    def setCoords(self):
        print( self.__dict__ )

И при запуске видим пустой список. Пропишем координаты в экземпляре pt:

pt.x = 5
pt.y = 10

Теперь, при запуске видим две записи для x и y. То есть, self действительно ссылается на тот же объект, что и pt.

Раз это так, то мы с помощью метода setCoords можем переопределять координаты нашей точки:

    def setCoords(self, x, y):
        self.x = x
        self.y = y

И, далее, вызвать этот метод:

pt.setCoords(5, 10)
print( pt.__dict__ )

Смотрите, мы здесь автоматически (через self) добавляем локальные атрибуты для экземпляра pt. То есть, указанные координаты будут принадлежать только объекту pt и никак не повлияют на значения других экземпляров. Что, в общем то, мы и ожидаем от использования объекта pt.

И здесь, опять же обратите внимание, что метод setCoords – общий для всех возможных экземпляров класса Point. Когда мы с вами на предыдущем занятии рассматривали переменные внутри класса, то об этом подробно говорили. Поведение методов абсолютно такое же: объявленные внутри класса они становятся общими для всех экземпляров, т.е. это будет одна и та же функция, а не копии внутри экземпляров. И только благодаря первому параметру self мы можем «знать» из какого конкретно экземпляра был вызван данный метод: self всегда ссылается на этот экземпляр. А вот, если вызвать этот метод непосредственно из класса:

Point.setCoords(5, 10)

то возникнет ошибка, т.к. в этом случае нужно явно указать первый аргумент:

Point.setCoords(pt, 5, 10)

Конструктор класса

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

__init__()

который вызывается при создании объекта:

    def __init__(self):
        print("создание экземпляра класса Point")

И, при выполнении:

pt = Point()

будет вызван данный метод. В ООП он имеет специальное название – конструктор. Итак, укажем в этом конструкторе еще два аргумента и создадим в нем локальные атрибуты:

    def __init__(self, x, y):
        self.x = x
        self.y = y

Теперь создать объект можно, только явно указав два аргумента:

pt = Point(5, 10)
print( pt.__dict__ )

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

Для большей гибкости, укажем в конструкторе аргументы x, y по умолчанию:

def __init__(self, x = 0, y = 0):

и тогда можно вызывать конструктор следующими способами:

pt = Point()
pt2 = Point(5)
pt3 = Point(5, 10)
print( pt.__dict__, pt2.__dict__, pt3.__dict__, sep="\n" )

Деструктор класса

Противоположный конструктору метод:

__del__()

автоматически вызывается при уничтожении экземпляра класса. Он называется деструктор.

Давайте запишем вот такой деструктор в наш класс Point:

    def __del__(self):
        print("Удаление экземпляра: "+self.__str__())

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

Но, в какой момент вообще происходит удаление объекта в Python? Алгоритм прост: пока на какой-либо объект имеется хотя бы одна внешняя ссылка, он продолжает существовать. Если же на объект нет внешних ссылок, он автоматически уничтожается «сборщиком мусора». Например, если создать экземпляр:

pt = Point()

то  на него будет ссылаться переменная pt. Но, при ее переопределении, например, так:

pt = 0

экземпляр класса Point в памяти остается без внешних ссылок и вскоре будет удален сборщиком мусора. При удалении, как раз и будет вызван деструктор класса __del__().

Задания для самоподготовки

1. Создайте класс Point3D, который хранит координаты в виде списка. Пропишите у него конструктор для создания экземпляров с локальными координатами. Также добавьте методы, позволяющие изменять координаты и получать их (в виде кортежа).

2. Объявите класс Point с конструктором, который бы позволял создавать экземпляр на основе другого, уже существующего. Если аргументы в конструктор не передаются, то создается объект с локальными атрибутами по умолчанию.

3. Напишите программу, в которой пользователь вводит координаты x, y с клавиатуры, создается соответствующий экземпляр и он сохраняется в списке. Количество вводимых объектов N=5. Затем, вывести их атрибуты в консоль.