0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>В прошлый раз, рассматривая принципы работы со<a>слотами в классах</a>, мы столкнулись с проблемой множественного наследования.</p>
1
<p>В прошлый раз, рассматривая принципы работы со<a>слотами в классах</a>, мы столкнулись с проблемой множественного наследования.</p>
2
<p>Суть проблемы заключалась в том, что если у двух классов определён атрибут __slots__, то создать от них общий дочерний класс не получится.</p>
2
<p>Суть проблемы заключалась в том, что если у двух классов определён атрибут __slots__, то создать от них общий дочерний класс не получится.</p>
3
class BaseA: __slots__ = ('a',) class BaseB: __slots__ = ('b',) >>> class Child(BaseA, BaseB): __slots__ = () Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: multiple bases have instance lay-out conflict<p>Можно, конечно, не указывать слоты в родительских классах и заполнить их только в дочернем, но это частный случай. Что же делать, если слоты нужны во всех трёх классах?</p>
3
class BaseA: __slots__ = ('a',) class BaseB: __slots__ = ('b',) >>> class Child(BaseA, BaseB): __slots__ = () Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: multiple bases have instance lay-out conflict<p>Можно, конечно, не указывать слоты в родительских классах и заполнить их только в дочернем, но это частный случай. Что же делать, если слоты нужны во всех трёх классах?</p>
4
<p>Как раз для таких (хотя и не только) случаев в ООП есть<strong>принцип абстрагирования</strong>. Правда, в Python на уровне абстракции не реализованы, но в стандартную библиотеку входит модуль abc -<strong>Abstract Base Classes</strong>.</p>
4
<p>Как раз для таких (хотя и не только) случаев в ООП есть<strong>принцип абстрагирования</strong>. Правда, в Python на уровне абстракции не реализованы, но в стандартную библиотеку входит модуль abc -<strong>Abstract Base Classes</strong>.</p>
5
<p>Абстрактный класс сам по себе нельзя инстанцировать - в нём определяется, какие методы и свойства нужно будет переопределить в дочерних классах.</p>
5
<p>Абстрактный класс сам по себе нельзя инстанцировать - в нём определяется, какие методы и свойства нужно будет переопределить в дочерних классах.</p>
6
from abc import ABC, abstractmethod, abstractproperty class AbstractBase(ABC): @abstractmethod def foo(self): pass @abstractproperty def baz(self): pass >>> AbstractBase() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class AbstractBase with abstract methods baz, foo class Base(AbstractBase): def foo(self): print('foo') @property def baz(self): return 'baz' >>> base = Base() >>> base.foo() foo >>> base.baz 'baz'<p>Благодаря использованию абстрактных классов мы можем проконтролировать, что все дочерние классы имеют одинаковый интерфейс. Проще всего это понять на примере.</p>
6
from abc import ABC, abstractmethod, abstractproperty class AbstractBase(ABC): @abstractmethod def foo(self): pass @abstractproperty def baz(self): pass >>> AbstractBase() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class AbstractBase with abstract methods baz, foo class Base(AbstractBase): def foo(self): print('foo') @property def baz(self): return 'baz' >>> base = Base() >>> base.foo() foo >>> base.baz 'baz'<p>Благодаря использованию абстрактных классов мы можем проконтролировать, что все дочерние классы имеют одинаковый интерфейс. Проще всего это понять на примере.</p>
7
class SerialPort(ABC): @abstractmethod def read(self): pass @abstractmethod def write(self): pass<p>Мы создали абстрактный класс<strong>SerialPort</strong>. Теперь, наследуя от него разные реализации (COM, USB, USB-C), мы не будем обязаны реализовать 2 базовых метода. Это будет гарантировать, что реализации всех конкретных классов можно использовать одинаково, не заботясь о том, какой именно последовательный порт используется.</p>
7
class SerialPort(ABC): @abstractmethod def read(self): pass @abstractmethod def write(self): pass<p>Мы создали абстрактный класс<strong>SerialPort</strong>. Теперь, наследуя от него разные реализации (COM, USB, USB-C), мы не будем обязаны реализовать 2 базовых метода. Это будет гарантировать, что реализации всех конкретных классов можно использовать одинаково, не заботясь о том, какой именно последовательный порт используется.</p>
8
class COM(SerialPort): pass >>> com = COM() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class COM with abstract methods read, write class COM(SerialPort): def read(self): return "" def write(self): pass >>> com = COM() >>> com.read() ''<p>Рассмотрим<strong>множественное наследование</strong>от абстрактных классов:</p>
8
class COM(SerialPort): pass >>> com = COM() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class COM with abstract methods read, write class COM(SerialPort): def read(self): return "" def write(self): pass >>> com = COM() >>> com.read() ''<p>Рассмотрим<strong>множественное наследование</strong>от абстрактных классов:</p>
9
class Charger(ABC): @abstractmethod def charge(self): pass class USB(SerialPort, Charger): def read(self): return "" def write(self): pass >>> usb = USB() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class USB with abstract methods charge<p>Ага, класс USB должен обязательно иметь имплементацию метода<strong>charge</strong>, поскольку это задекларировано в родительском классе<strong>Charger</strong>:</p>
9
class Charger(ABC): @abstractmethod def charge(self): pass class USB(SerialPort, Charger): def read(self): return "" def write(self): pass >>> usb = USB() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class USB with abstract methods charge<p>Ага, класс USB должен обязательно иметь имплементацию метода<strong>charge</strong>, поскольку это задекларировано в родительском классе<strong>Charger</strong>:</p>
10
class USB(SerialPort, Charger): def read(self): return "" def write(self): pass def charge(self): print('Charging') >>> usb = USB() >>> usb.charge() Charging<p>Возвращаясь к проблеме со __slots__, решение через абстрактные классы выглядит примерно так:</p>
10
class USB(SerialPort, Charger): def read(self): return "" def write(self): pass def charge(self): print('Charging') >>> usb = USB() >>> usb.charge() Charging<p>Возвращаясь к проблеме со __slots__, решение через абстрактные классы выглядит примерно так:</p>
11
class AbstractA(ABC): __slots__ = () class AbstractB(ABC): __slots__ = () class BaseA(AbstractA): __slots__ = ('a',) class BaseB(AbstractB): __slots__ = ('b',) class Child(AbstractA, AbstractB): __slots__ = ('a', 'b') c = Child()<p>То есть мы вместо того, чтобы наследоваться от классов с конкретной реализацией (BaseA, BaseB),<strong>наследуемся от абстрактных классов</strong>. Таким образом, мы, гарантируя совместимость интерфейсов, определяем независимые слоты для каждого из классов.</p>
11
class AbstractA(ABC): __slots__ = () class AbstractB(ABC): __slots__ = () class BaseA(AbstractA): __slots__ = ('a',) class BaseB(AbstractB): __slots__ = ('b',) class Child(AbstractA, AbstractB): __slots__ = ('a', 'b') c = Child()<p>То есть мы вместо того, чтобы наследоваться от классов с конкретной реализацией (BaseA, BaseB),<strong>наследуемся от абстрактных классов</strong>. Таким образом, мы, гарантируя совместимость интерфейсов, определяем независимые слоты для каждого из классов.</p>
12
12