Абстрактные классы в Python | OTUS
⚡ Подписка на курсы OTUS!
Интенсивная прокачка навыков для IT-специалистов!
Подробнее

Курсы

Программирование
Алгоритмы и структуры данных Team Lead Архитектура и шаблоны проектирования Разработчик IoT C# Developer. Professional PostgreSQL Разработчик на Spring Framework
-5%
Flutter Mobile Developer NoSQL iOS Developer. Basic
-10%
C++ Developer. Basic C++ Developer. Professional Android Developer. Professional Microservice Architecture Unity Game Developer. Professional Базы данных Node.js Developer React.js Developer Специализация Java-разработчик
-25%
Web-разработчик на Python Framework Laravel Cloud Solution Architecture Vue.js разработчик Интенсив «Оптимизация в Java» Супер - интенсив по паттернам проектирования Супер - интенсив по Kubernetes Супер-интенсив "Tarantool" PHP Developer. Basic
Инфраструктура
Мониторинг и логирование: Zabbix, Prometheus, ELK Administrator Linux. Professional Дизайн сетей ЦОД Разработчик IoT PostgreSQL Экспресс-курс "Версионирование и командная работа с помощью Git"
-30%
Microservice Architecture Highload Architect Специализация Administrator Linux
-25%
Network engineer Cloud Solution Architecture Внедрение и работа в DevSecOps Супер-практикум по работе с протоколом BGP Супер - интенсив по паттернам проектирования Супер - интенсив по Kubernetes Супер-интенсив «СУБД в высоконагруженных системах» Супер-интенсив "Tarantool" Network engineer. Basic
Корпоративные курсы
Безопасность веб-приложений IT-Recruiter Дизайн сетей ЦОД Компьютерное зрение Разработчик IoT Вебинар CERTIPORT Machine Learning. Professional
-6%
NoSQL Пентест. Практика тестирования на проникновение Java QA Engineer. Базовый курс Руководитель поддержки пользователей в IT
-8%
SRE практики и инструменты Cloud Solution Architecture Внедрение и работа в DevSecOps Супер-практикум по работе с протоколом BGP Infrastructure as a code Супер-практикум по использованию и настройке GIT Промышленный ML на больших данных Экспресс-курс «CI/CD или Непрерывная поставка с Docker и Kubernetes» BPMN: Моделирование бизнес-процессов Основы Windows Server
Специализации Курсы в разработке Подготовительные курсы Подписка
+7 499 938-92-02

Абстрактные классы в Python

WebDev_Deep_6.5_site-5020-508885.png

В прошлый раз, рассматривая принципы работы со слотами в классах, мы столкнулись с проблемой множественного наследования. Суть проблемы заключалась в том, что если у двух классов определён атрибут __slots__, то создать от них общий дочерний класс не получится.

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

Можно, конечно, не указывать слоты в родительских классах и заполнить их только в дочернем, но это частный случай. Что же делать, если слоты нужны во всех трёх классах?

Как раз для таких (хотя и не только) случаев в ООП есть принцип абстрагирования. Правда, в Python на уровне абстракции не реализованы, но в стандартную библиотеку входит модуль abc — Abstract Base Classes.

Абстрактный класс сам по себе нельзя инстанцировать — в нём определяется, какие методы и свойства нужно будет переопределить в дочерних классах.

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'

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

class SerialPort(ABC):
    @abstractmethod
    def read(self):
        pass
    @abstractmethod
    def write(self):
        pass

Мы создали абстрактный класс SerialPort. Теперь, наследуя от него разные реализации (COM, USB, USB-C), мы не будем обязаны реализовать 2 базовых метода. Это будет гарантировать, что реализации всех конкретных классов можно использовать одинаково, не заботясь о том, какой именно последовательный порт используется.

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()
''

Рассмотрим множественное наследование от абстрактных классов:

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

Ага, класс USB должен обязательно иметь имплементацию метода charge, поскольку это задекларировано в родительском классе Charger:

class USB(SerialPort, Charger):
    def read(self):
        return ""
    def write(self):
         pass
    def charge(self):
         print('Charging')

>>> usb = USB()
>>> usb.charge()
Charging

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

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()

То есть мы вместо того, чтобы наследоваться от классов с конкретной реализацией (BaseA, BaseB), наследуемся от абстрактных классов. Таким образом, мы, гарантируя совместимость интерфейсов, определяем независимые слоты для каждого из классов.

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
1 комментарий
0

Здравствуйте! Очень интересная статья. После прочтения решил померить размер экземпляров классов при помощи pympler.asizeof: - наследованный от одного класса на slots 16b - наследованный от двух классов на dict 168b - наследованный от двух абстрактных классов на slots - 440b !!! Соответственно вопрос: Какой смысл в таком наследовании?

Для комментирования необходимо авторизоваться