Курс по Python ООП: https://stepik.org/a/116336
Мы продолжаем
изучение коллекции __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 = (x*x + y*y) ** 0.5
И, далее,
создавая экземпляр этого класса:
автоматически получаем
свойство 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 – это не локальная переменная
экземпляра класса, а атрибут класса Point2D. Коллекция __slots__ не
накладывает ограничения на атрибуты класса, только на локальные атрибуты его
экземпляров. Благодаря этому и появляется свойство length, которое
работает как геттер и сеттер класса Point2D.
Поведение __slots__ при наследовании классов
Следующий важный
момент – это особенности работы коллекции __slots__ при
наследовании классов. Я сразу приведу пример, чтобы показать первую особенность
этой коллекции. Допустим, мы создаем еще один класс Point3D, который
наследует от класса Point2D и он пока будет
пустым:
class Point3D(Point2D):
pass
Тогда, для
экземпляров этого класса будет разрешено создание локальных свойств, несмотря
на то, что в базовом классе присутствует коллекция __slots__:
pt3 = Point3D(10, 20)
pt3.z = 30
То есть, по
умолчанию эта коллекция не наследуется дочерними классами и они ведут себя как
обычные, без каких-либо ограничений, сохраняя все локальные свойства, используя
коллекцию:
Однако, если мы
ее пропишем даже без указания каких-либо элементов:
class Point3D(Point2D):
__slots__ = ()
то ограничения
вступят в свои права и она будет как бы унаследована от базового класса Point2D. То есть, в
классе Point3D сейчас доступны
только два локальных свойства x и y.
Но нам нужно
разрешить еще одно имя z. Для этого достаточно одно его и
прописать в этой коллекции:
class Point3D(Point2D):
__slots__ = 'z',
Тогда имена x, y будут
наследоваться от класса Point2D, а z – добавляться
классом Point3D. Также обратите
внимание на висячую запятую. Она означает, что z – это элемент
кортежа, а не просто строка. Предпочтительно записывать в таком виде. Хотя, и
без запятой тоже все будет работать.
Очевидно,
следующим шагом надо добавить конструктор в дочерний класс. Запишем его в
следующем виде:
class Point3D(Point2D):
__slots__ = 'z',
def __init__(self, x, y, z):
super().__init__(x, y)
self.z = z
И экземпляр
будем создавать уже с тремя параметрами:
pt3 = Point3D(10, 20, 30)
Теперь, наш
дочерний класс содержит уникальный код, расширяющий функционал базового класса
без каких-либо повторений. При этом, в обоих классах используется ограничение,
но с разным набором локальных свойств.
Надеюсь, из этих
занятий вы стали лучше понимать работу этой коллекции и при необходимости
применять ее в своих проектах.
Курс по Python ООП: https://stepik.org/a/116336