Абстрактные классы в Python | OTUS
🔥 BLACK FRIDAY!
Максимальная скидка -25% на всё. Успейте начать обучение по самой выгодной цене.
Выбрать курс

Курсы

Программирование
iOS Developer. Basic
-25%
Python Developer. Professional
-25%
Разработчик на Spring Framework
-25%
Golang Developer. Professional
-25%
Python Developer. Basic
-25%
iOS Developer. Professional
-25%
Highload Architect
-25%
JavaScript Developer. Basic
-25%
Kotlin Backend Developer
-25%
JavaScript Developer. Professional
-25%
Android Developer. Basic
-25%
Unity Game Developer. Basic
-25%
Разработчик C#
-25%
Программист С Web-разработчик на Python Алгоритмы и структуры данных Framework Laravel PostgreSQL Reverse-Engineering. Professional CI/CD Vue.js разработчик VOIP инженер Программист 1С Flutter Mobile Developer Супер - интенсив по Kubernetes Symfony Framework Advanced Fullstack JavaScript developer Супер-интенсив "Azure для разработчиков"
Инфраструктура
Мониторинг и логирование: Zabbix, Prometheus, ELK
-25%
DevOps практики и инструменты
-25%
Архитектор сетей
-25%
Инфраструктурная платформа на основе Kubernetes
-25%
Супер-интенсив «IaC Ansible»
-16%
Разработчик программных роботов (RPA) на базе UiPath и PIX
-25%
Супер-интенсив "SQL для анализа данных"
-16%
Базы данных Сетевой инженер AWS для разработчиков Cloud Solution Architecture Разработчик голосовых ассистентов и чат-ботов Внедрение и работа в DevSecOps Администратор Linux. Виртуализация и кластеризация Нереляционные базы данных Супер-практикум по использованию и настройке GIT IoT-разработчик Супер-интенсив «ELK»
Специализации Курсы в разработке Подготовительные курсы
+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 !!! Соответственно вопрос: Какой смысл в таком наследовании?

Для комментирования необходимо авторизоваться
🎁 Максимальная скидка!
Черная пятница уже в OTUS! Скидка -25% на всё!