Интерфейсы Python: откажитесь от ABC и переключитесь на протоколы

Интерфейсы Python: откажитесь от ABC и переключитесь на протоколы

10 февраля 2023 г.

Несколько практических причин предпочесть новые блестящие протоколы

Я использовал стандартную библиотеку Python abc для определения интерфейсов в течение последних 10 лет своей карьеры. Но недавно я обнаружил, что относительно новые протоколы Python намного лучше.

Люди находят применение обеим технологиям.

Но я хочу убедить вас полностью покинуть корабли и начать использовать их вместо более традиционных методов.

Интерфейсы Python: что можно использовать?

Python несколько отличается от других популярных языков, поскольку в нем нет интерфейсов на языковом уровне.

Однако существует несколько реализаций библиотек:

* <код>абв * ввод.Протоколы * сторонние реализации, такие как Zope * пользовательские реализации (например, через метаклассы)

Пакет

abc, пожалуй, самый популярный:

from abc import ABC, abstractmethod

class Animal(ABC):
   @abstractmethod
   def eat(self, food) -> float:
       pass

   @abstractmethod
   def sleep(self, hours) -> float:
       pass

Далее, наиболее часто упоминаемым пакетом является Zope:

from zope.interface import Interface

class Animal(Interface):
   def eat(self, food) -> float:
       pass

   def sleep(self, hours) -> float:
       pass

Zope — это веб-библиотека, и ее интерфейсы имеют множество дополнительных функций.

Кроме того, в Интернете есть дополнительные пользовательские пакеты и руководства по самостоятельному созданию системы интерфейса (см. пример). .

Наконец, есть протоколы:

from typing import Protocol

class Animal(Protocol):
   def eat(self, food) -> float:
       ...

   def sleep(self, hours) -> float:
       ...

Протокол — это формализация идеологии Python "утиного набора". Есть много отличных статей о структурной типизации в Python. (это, это и некоторые обсуждения). р>

Возможно, протоколы и интерфейсы — это теоретически разные звери, но протокол выполняет свою работу. Мне удалось заменить abc на Protocols без каких-либо недостатков.

Что следует использовать?

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

Поэтому я бы сразу отклонил любые пользовательские реализации или Zope.

Это дополнительная зависимость, с которой вам придется иметь дело вечно: установка, версии, поддержка и так далее. Например, для поддержки < code>zope.interface хорошо. Кроме того, новый разработчик в команде может не знать этот специальный пакет, и вам придется объяснить, что это такое и почему вы его выбрали. Основная битва развернется между abc и Протоколами.

Но если вы действительно хотите сразиться между zope и Protocols, прочитать это (содержит подробный анализ преимуществ zope во время выполнения).

Сначала проверка статики

Основное предположение, которое я собираюсь сделать, заключается в том, что вы уже убеждены в том, что статическая проверка обязательна:

вы не собираетесь запускать код, который не работает pylint/mypy. Обе программы проверки одинаково хорошо поддерживают abc и Protocols. Кроме того, просто знайте, что и abc, и Протоколы допускают проверку во время выполнения, если вам это нужно.

Оба поддерживают явный синтаксис

Во-первых, обратите внимание, что вы по-прежнему можете явно наследовать от abc и от Protocol. Многие аргументы в очень хорошее видео (и комментарии) с сайта Арджан вращаются вокруг заблуждения что вы не можете сделать это с протоколами.

Вы можете:

class Giraffe(Animal):
   ...

Таким образом, в этом отношении abc и протоколы можно использовать одинаково. Однако протоколы по умолчанию дают вам дополнительную степень свободы проектирования.

Вы можете избежать явного наследования, но при этом наслаждаться полной проверкой интерфейса:

class Giraffe:  # no base class needed!
   def eat(self, food) -> float:
       return 0.

   def sleep(self, hours) -> float:
       return 1.

def feed_animal(animal: Animal):
    ... 

giraffe = Giraffe()
feed_animal(giraffe)

Это позволяет вам создать интерфейс для кода, который вы не контролируете, и ослабить зависимости между модулями в вашей кодовой базе. Выбор скрытого или явного варианта — это тонкий выбор, который решается в каждом конкретном случае. Хороший пример явного согласия на использование интерфейса описан здесь.

Протоколы не заставляют вас соглашаться, но вы можете установить общекорпоративное правило для явного наследования от любого протокола.

abc также поддерживает неявные интерфейсы посредством концепции "виртуальных подклассов" . Но вы должны вызывать register для каждой реализации:

Procotol поддерживает неявные и явные варианты без дополнительного синтаксиса и работает с mypy. Кроме того, mypy не поддерживает register с конца 2022 года.

Я не уверен, что мы можем полностью считать это преимуществом abc.

Протоколы позволяют определить интерфейс для функции (а не только для класса).

Это очень классная функция, достойная отдельного поста.

Оба поддерживают методы по умолчанию :(

К сожалению, у abc и Protocols есть большой недостаток. В реальном мире многие люди работают с одной кодовой базой. Абстрактные базовые классы иногда имеют тенденцию приобретать реализации методов по умолчанию.

Вот как это может выглядеть:

В этом случае они перестают быть «абстрактными» и становятся просто базовыми классами.

Python и статические средства проверки этого не улавливают. Дизайн программного обеспечения с наследованием на самом деле не то же самое, что дизайн с интерфейсами. Я бы хотел, чтобы Python разделил их на уровне языка, но вряд ли это произойдет. Неявные протоколы имеют здесь преимущество. Они позволяют полностью избежать беспорядочного наследования.

Протоколы короче

И последнее, но не менее важное: вы можете подсчитать количество строк кода, необходимых для определения интерфейса. С abc у вас должен быть декоратор abstractmethod для каждого метода. Но с протоколами без проверки во время выполнения вам вообще не нужно использовать какие-либо декораторы. Так что протоколы побеждают безоговорочно.

Заключение

Суммируем баллы:

| Возможности | азбука | Протоколы | |:---|:---:|:---:| | Проверка во время выполнения | 1 | 1 | | Статическая проверка | 1 | 1 | | Явный интерфейс с наследованием | 1 | 1 | | Неявный интерфейс без наследования (для abc требуется register) | 0,5 | 1 | | Возможность реализации метода по умолчанию | -1 | -1 | | Интерфейс обратного вызова | 0 | 1 | | Количество строк | -1 | 0 | | | | | | Итого | 1,5 | 4 |

Надеюсь, я не упустил ничего важного в этом анализе. Спасибо за чтение! Судя по результатам, команда "Протоколы" побеждает, и вам, вероятно, стоит просто начать ее использовать!

Спасибо, что прочитали! Вы можете найти меня на LinkedIn или Твиттер.


Первоначально опубликовано здесь


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE