Создание объектов-свойств

Это занятие является продолжением предыдущего, где мы рассматривали приватные атрибуты, сеттеры и геттеры, а также контроль за их изменением при помощи перегрузки некоторых базовых методов. Однако, пользоваться на практике напрямую геттерами и сеттерами бывает не всегда удобно. Большего изящества кода можно добиться, используя так называемые объекты-свойства (property). Например, мы хотим создать в классе Point свойство

coordX

которому можно было бы присваивать и считывать значения подобно обычной переменной:

pt.coordX = 100 # запись значения
x = pt.coordX   # чтение значения

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

def __getCoordX(self):
    print("вызов __getCoordX")
    return self.__x
 
def __setCoordX(self, x):
    print("вызов __setCoordX")
    self.__x = x

То есть, при вызове геттера мы возвращаем значение приватного свойства__x, а при вызове сеттера – заносим новое значение в этот атрибут. На основе этих приватных методов создаем свойство через специальный класс property:

coordX = property(__getCoordX, __setCoordX)

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

pt = Point()
print( pt.coordX )
pt.coordX = 100
print( pt.coordX )

Видите, это намного удобнее, чем вызывать отдельно сеттеры и геттеры.

Теперь уберем из геттера и сеттера функцию print и добавим проверку на корректность передаваемых данных:

def __getCoordX(self):
    return self.__x
 
def __setCoordX(self, x):
    if Point.__checkValue(x):
        self.__x = x
    else:
        raise ValueError("Неверный формат данных")

Теперь, если попытаться передать не числовое значение:

pt.coordX = "100"

то возникнет исключение ValueError.

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

def __delCoordX(self):
    print("Удаление свойства")
    del self.__x

Он указывается третьим параметром при вызове класса property, а четвертым можно указать описание свойства:

coordX = property(__getCoordX, __setCoordX, __delCoordX, "Работа с X")

Если теперь выполнить удаление свойства:

del pt.coordX

то увидим сообщение «Удаление свойства» и дальнейшая попытка к его обращению:

pt.coordX

приведет к ошибке, т.к. приватного свойства __x уже не существует. Вот так работают и создаются свойства в Python. И давайте здесь же я вам покажу еще один способ объявления свойств через декораторы (если вы не знаете что такое декоратор, то смотрите урок по этой теме – ссылка под этим видео).

Перед геттером мы укажем декоратор

@property

И название метода должно совпадать с названием свойства:

def coordX(self):

Далее, у сеттера указываем декоратор с то же имя метода:

@coordX.setter
def coordX(self, x):

То есть, пишем имя нашего свойства и через точку зарезервированное имя setter. Ну а перед делитером декоратор

@coordX.deleter
def coordX(self):

Все, теперь мы абсолютно также можем работать со свойством coordX:

pt = Point()
print( pt.coordX )
pt.coordX = 100
print( pt.coordX )
del pt.coordX

Хорошо, свойство coordX у нас есть. Но нам нужно создать еще одно – coordY. Как вы понимаете, это, фактически, приведет к дублированию кода для coordX, что не есть хорошо, так как нарушает принцип программирования

DRY (Don’t Repeat Yourself) – не повторяйся!

И здесь нам на помощь приходит другой механизм Python – дескрипторы, о котором речь пойдет на следующем занятии.