Интерфейсы 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
для каждой реализации:
class Giraffe: # no base class needed!
def eat(self, food) -> float:
return 0.
class Animal(ABC):
...
Animal.register(Giraffe) # achieves the same as implicit Protocol
Procotol
поддерживает неявные и явные варианты без дополнительного синтаксиса и работает с mypy
.
Кроме того, mypy
не поддерживает register
с конца 2022 года.
Я не уверен, что мы можем полностью считать это преимуществом abc
.
Протоколы позволяют определить интерфейс для функции (а не только для класса).
Это очень классная функция, достойная отдельного поста.
Оба поддерживают методы по умолчанию :(
К сожалению, у abc
и Protocols
есть большой недостаток. В реальном мире многие люди работают с одной кодовой базой. Абстрактные базовые классы иногда имеют тенденцию приобретать реализации методов по умолчанию.
Вот как это может выглядеть:
class Animal(Protocol): # the same holds for Animal(ABC):
def eat(self, food) -> float:
... # this is still abstract
def sleep(self, hours) -> float:
return 3.
В этом случае они перестают быть «абстрактными» и становятся просто базовыми классами.
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 или Твиттер.
Первоначально опубликовано здесь
Оригинал