HTML Diff
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',) &gt;&gt;&gt; class Child(BaseA, BaseB): __slots__ = () Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; TypeError: multiple bases have instance lay-out conflict<p>Можно, конечно, не указывать слоты в родительских классах и заполнить их только в дочернем, но это частный случай. Что же делать, если слоты нужны во всех трёх классах?</p>
3 class BaseA: __slots__ = ('a',) class BaseB: __slots__ = ('b',) &gt;&gt;&gt; class Child(BaseA, BaseB): __slots__ = () Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; 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 &gt;&gt;&gt; AbstractBase() Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; 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' &gt;&gt;&gt; base = Base() &gt;&gt;&gt; base.foo() foo &gt;&gt;&gt; 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 &gt;&gt;&gt; AbstractBase() Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; 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' &gt;&gt;&gt; base = Base() &gt;&gt;&gt; base.foo() foo &gt;&gt;&gt; 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 &gt;&gt;&gt; com = COM() Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; TypeError: Can't instantiate abstract class COM with abstract methods read, write class COM(SerialPort): def read(self): return "" def write(self): pass &gt;&gt;&gt; com = COM() &gt;&gt;&gt; com.read() ''<p>Рассмотрим<strong>множественное наследование</strong>от абстрактных классов:</p>
8 class COM(SerialPort): pass &gt;&gt;&gt; com = COM() Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; TypeError: Can't instantiate abstract class COM with abstract methods read, write class COM(SerialPort): def read(self): return "" def write(self): pass &gt;&gt;&gt; com = COM() &gt;&gt;&gt; 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 &gt;&gt;&gt; usb = USB() Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; 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 &gt;&gt;&gt; usb = USB() Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; 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') &gt;&gt;&gt; usb = USB() &gt;&gt;&gt; usb.charge() Charging<p>Возвращаясь к проблеме со __slots__, решение через абстрактные классы выглядит примерно так:</p>
10 class USB(SerialPort, Charger): def read(self): return "" def write(self): pass def charge(self): print('Charging') &gt;&gt;&gt; usb = USB() &gt;&gt;&gt; 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