Как работает __slots__ с property и при наследовании классов

Продолжаем изучение коллекции __slots__. Как мы говорили на предыдущем занятии, эта коллекция позволяет ограничить список локальных свойств в экземплярах класса, в котором прописана. Например, для класса точки на плоскости мы разрешали только свойства с именами x и y:

class Point2D:
    __slots__ = ('x', 'y')
 
    def __init__(self, x, y):
        self.x = x
        self.y = y

А теперь, сделаем следующее. Добавим в эту коллекцию еще одно имя:

__slots__ = ('x', 'y', 'length')

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

class Point2D:
    __slots__ = ('x', 'y', 'length')
 
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.length = math.sqrt(x*x + y*y)

И, далее, создавая экземпляр этого класса:

pt = Point2D(1, 2)

мы автоматически получаем свойство length с расстоянием до точки (относительно нуля):

pt.length

Но что если в этом классе прописать свойство (property) с тем же самым именем length? А в коллекции __slots__ задать приватное свойство:

__slots__ = ('x', 'y', '__length')

Давайте добавим это свойство и посмотрим, что получится:

    @property
    def length(self):
        return self.__length
 
    @length.setter
    def length(self, value):
        self.__length = value

И, затем, после создания экземпляра класса, мы можем использовать такой синтаксис:

pt = Point2D(1, 2)
print( pt.length )

Смотрите, мы здесь обращаемся к свойству length, хотя в __slots__ нет такого разрешенного имени. И при этом нет никаких ошибок выполнения. В принципе, так и должно быть, потому что length – это не локальная переменная экземпляра класса, а именно свойство, определяемое через метод length и декоратор property. Коллекция __slots__ не накладывает ограничения на имена методов класса и мы можем в нем прописывать самые разные функции. Благодаря этому и появляется свойство length, которое работает как геттер и сеттер класса Point2D.

Поведение __slots__ при наследовании классов

Следующий важный момент – это особенности работы коллекции __slots__ при наследовании классов. Я сразу приведу пример, чтобы показать первую особенность этой коллекции. Допустим, мы создаем еще один класс Point3D, который наследует от класса Point2D и он пока будет пустым:

class Point3D(Point2D):
    pass

Тогда, для экземпляров этого класса будет разрешено создание локальных свойств, несмотря на то, что в базовом классе присутствует коллекция __slots__:

pt3 = Point3D(10, 20)
pt3.z = 30

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

pt3.__dict__

Но, давайте, добавим ограничения на локальные свойства и разрешим использовать только имена x, y и z. Казалось бы, для этого в дочернем классе нужно прописать коллекцию:

class Point3D(Point2D):
    __slots__ = 'x', 'y', 'z'

И это будет работать. Однако, появляется дублирование кода: в базовом классе мы уже запрещали x и y, поэтому в дочернем их не стоит повторять, а указать одно дополнительное имя z:

class Point3D(Point2D):
    __slots__ = 'z',

В итоге, мы ушли от дублирования и сохранили прежнюю функциональность. Также обратите внимание на висячую запятую. Она означает, что z – это элемент кортежа, а не просто строка. Предпочтительно записывать в таком виде. Хотя, и без запятой тоже все будет работать.

Очевидно, следующим шагом надо добавить конструктор в дочерний класс. Запишем его в следующем виде:

class Point3D(Point2D):
    __slots__ = 'z',
 
    def __init__(self, x, y, z):
        super().__init__(x, y)
        self.z = z

И экземпляр будем создавать уже с тремя параметрами:

pt3 = Point3D(10, 20, 30)

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

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

Видео по теме