В чем основной недостаток использования абстрактных классов для реализации интерфейсов в Python

Ответ

Основной недостаток в том, что проверка на полную реализацию интерфейса происходит во время выполнения (runtime) при попытке создать экземпляр класса, а не на этапе статического анализа или определения класса.

Это означает, что ошибка из-за нереализованного абстрактного метода может проявиться только после запуска приложения, а не во время разработки или компиляции.

Пример:

from abc import ABC, abstractmethod

class StorageInterface(ABC):
    @abstractmethod
    def save(self, data: bytes):
        pass

    @abstractmethod
    def load(self, key: str) -> bytes:
        pass

# Класс определен без ошибок, хотя метод load() не реализован
class FileStorage(StorageInterface):
    def save(self, data: bytes):
        print("Data saved to file.")

# Ошибка возникнет только здесь, при попытке создания объекта
# TypeError: Can't instantiate abstract class FileStorage 
# with abstract method load
try:
    storage = FileStorage()
except TypeError as e:
    print(e)

Ключевые недостатки:

  • Позднее обнаружение ошибок: Ошибки выявляются только при инстанцировании, а не при определении класса.
  • Отсутствие проверки сигнатур: abc не проверяет соответствие типов и количества аргументов в реализованных методах.
  • Жесткая связь: Класс-реализация должен явно наследоваться от абстрактного класса, что создает сильную связь в иерархии классов.

Современная альтернатива:

Для определения интерфейсов в современном Python предпочтительнее использовать Протоколы (typing.Protocol), введенные в PEP 544. Они поддерживают статическую типизацию (утиная типизация) и позволяют статическим анализаторам (как Mypy) выявлять несоответствия интерфейсу еще до запуска кода.

Ответ 18+ 🔞

А, слушай, вот эта ваша абстрактная хуйня в питоне, она же, блядь, чисто на доверии работает! Как будто в рот меня чих-пых, честное пионерское!

Смотри, в чём прикол-то. Ты объявляешь интерфейс, там методы всякие, и думаешь: "Всё, щас компилятор за мной проследит, чтобы я ничего не забыл". А нихуя! Ошибка-то вылезет не когда ты пишешь класс, а только когда ты его, сука, создавать пытаешься! Во время работы программы, ёпта!

Вот смотри, пример, блядь:

from abc import ABC, abstractmethod

class StorageInterface(ABC):
    @abstractmethod
    def save(self, data: bytes):
        pass

    @abstractmethod
    def load(self, key: str) -> bytes:
        pass

# Смотри, я тут накосячил, метод load не написал. Но IDE молчит, как партизан!
class FileStorage(StorageInterface):
    def save(self, data: bytes):
        print("Data saved to file.")

# И вот тут всё ещё тишина, блядь! Всё компилируется!
# А пиздец наступит вот здесь:
try:
    storage = FileStorage()  # БАБАХ! TypeError вылезет только сейчас!
except TypeError as e:
    print(e)

Вот тебе и "интерфейс", мать его! Ошибка как мина замедленного действия — сидит и ждёт, пока ты код запустишь. Представь, ты уже на прод выкатил, а тут — оп-па! — класс не инстанциируется. Красота, да?

Короче, главные косяки этой схемы:

  • Ошибки ловятся поздно, как поезд. Не при написании, а при запуске. Удобно, блядь, особенно если код редко запускаемый.
  • Сигнатуры не проверяет. Можешь метод назвать правильно, но аргументы там наворотить какие угодно — abc промолчит, как будто так и надо. Пиздец, а не контроль.
  • Жёстко привязывает по родству. Наследник должен быть прям сыном абстрактного класса, иначе нихуя не работает. Архитектура, блядь, деревянная получается.

Так что делать-то, спросишь? А есть же, блядь, современная штука — Протоколы (typing.Protocol). Вот это уже ближе к правде жизни. Они со статическими анализаторами (типа Mypy) дружат. Ты код пишешь, а тебе сразу подсказывают: "Э, дружок-пирожок, а метод-то load ты забыл, пидарас шерстяной!". Вот это уже дело. Утиная типизация в её лучшем, ебать, проявлении.