Укороченная версия
Азбука предлагает более высокий уровень семантического контракта между клиентами и внедренными классами.
Длинная версия
Существует договор между классом и его посетителями. Класс обещает делать определенные вещи и обладать определенными свойствами.
Существуют разные уровни контракта.
На очень низком уровне контракт может содержать название метода или его количество параметров.
В языке со статической типизацией этот контракт будет фактически выполняться компилятором. В Python вы можете использовать EAFP или ввести самоанализ, чтобы подтвердить, что неизвестный объект соответствует этому ожидаемому контракту.
Но в контракте также есть семантические обещания более высокого уровня.
Например, если есть __str__()
метод, ожидается, что он вернет строковое представление объекта. Он может удалить все содержимое объекта, зафиксировать транзакцию и выплюнуть пустую страницу из принтера ... но существует общее понимание того, что он должен делать, описанное в руководстве по Python.
Это особый случай, когда семантический контракт описан в руководстве. Что должен print()
делать метод? Должен ли он записать объект на принтер или строку на экран, или что-то еще? Это зависит - вам нужно прочитать комментарии, чтобы понять полный контракт здесь. Часть клиентского кода, которая просто проверяет, print()
существует ли метод, подтвердила часть контракта - что вызов метода может быть выполнен, но не то, что существует соглашение о семантике вызова более высокого уровня.
Определение абстрактного базового класса (ABC) - это способ создания контракта между реализациями класса и вызывающими. Это не просто список имен методов, а общее понимание того, что эти методы должны делать. Если вы наследуете от этого ABC, вы обещаете следовать всем правилам, описанным в комментариях, включая семантику print()
метода.
Утиная печать Python имеет много преимуществ в гибкости по сравнению со статической, но она не решает всех проблем. Азбука предлагает промежуточное решение между свободной формой Python и связыванием и дисциплиной статически типизированного языка.
__contains__
и классом, который наследуетсяcollections.Container
? В вашем примере в Python всегда было общее понимание__str__
. Реализация__str__
дает те же обещания, что и наследование от некоторого ABC, а затем реализация__str__
. В обоих случаях вы можете нарушить договор; нет доказуемой семантики, такой как у нас в статической типизации.collections.Container
является вырожденным случаем, который включает в себя\_\_contains\_\_
и только означает предопределенное соглашение. Я согласен, что использование ABC само по себе не приносит особой пользы. Я подозреваю, что это было добавлено, чтобы позволить (например)Set
наследовать от него. К тому времени, когда вы добираетесь до местаSet
, внезапно принадлежность к ABC имеет значительную семантику. Предмет не может принадлежать коллекции дважды. Это НЕ обнаруживается существованием методов.Set
, это лучший пример, чемprint()
. Я пытался найти имя метода, значение которого было неоднозначным, и его нельзя было обмануть одним только именем, поэтому вы не могли быть уверены, что он будет делать правильные вещи только по его имени и руководству по Python.Set
в качестве примера вместоprint
?Set
имеет много смысла, @Oddthinking.@ Ответ Oddthinking не является неправильным, но я думаю , что не попадает в реальный , практический разум Python имеет азбуку в мире утка-типирования.
Абстрактные методы аккуратны, но, на мой взгляд, они на самом деле не заполняют ни одного варианта использования, еще не охваченного типизацией утки. Реальная сила абстрактных базовых классов заключается в том, как они позволяют настраивать поведение
isinstance
иissubclass
. (__subclasshook__
в основном это более дружественный API поверх Python__instancecheck__
и__subclasscheck__
хуков.) Адаптация встроенных конструкций для работы с пользовательскими типами является очень важной частью философии Python.Исходный код Python является образцовым. Вот как
collections.Container
это определено в стандартной библиотеке (на момент написания):Это определение
__subclasshook__
говорит о том, что любой класс с__contains__
атрибутом считается подклассом контейнера, даже если он не подкласс его напрямую. Так что я могу написать это:Другими словами, если вы реализуете правильный интерфейс, вы подкласс! ABC предоставляют формальный способ определения интерфейсов в Python, оставаясь верным духу утиной типизации. Кроме того, это работает таким образом, чтобы соблюдать принцип Open-Closed .
Объектная модель Python внешне похожа на модель более «традиционной» ОО-системы (под которой я имею в виду Java *) - у нас есть ваши классы, ваши объекты, ваши методы - но когда вы поцарапаете поверхность, вы обнаружите что-то гораздо более богатое и более гибкий. Аналогично, понятие Python об абстрактных базовых классах может быть узнаваемым для разработчика Java, но на практике они предназначены для совсем другой цели.
Я иногда нахожу себя пишущим полиморфные функции, которые могут воздействовать на отдельный элемент или набор элементов, и я нахожу,
isinstance(x, collections.Iterable)
что гораздо более читабельным, чемhasattr(x, '__iter__')
или эквивалентныйtry...except
блок. (Если бы вы не знали Python, какой из этих трех определил бы смысл кода?)Тем не менее, я нахожу, что мне редко нужно писать свой собственный ABC, и я обычно обнаруживаю потребность в нем посредством рефакторинга. Если я вижу полиморфную функцию, выполняющую много проверок атрибутов, или множество функций, выполняющих одинаковые проверки атрибутов, этот запах предполагает существование ABC, ожидающей извлечения.
* не вдаваясь в споры о том, является ли Java «традиционной» ОО-системой ...
Приложение : Хотя абстрактный базовый класс может переопределять поведение
isinstance
иissubclass
, он все равно не входит в MRO виртуального подкласса. Это потенциальная ловушка для клиентов: не каждый объект, для которогоisinstance(x, MyABC) == True
определены методыMyABC
.К сожалению, это одна из тех ловушек «просто не делай этого» (которых у Python относительно мало!): Избегайте определения ABC как a, так
__subclasshook__
и неабстрактными методами. Более того, вы должны привести свое определение в__subclasshook__
соответствие с набором абстрактных методов, которые определяет ваша ABC.источник
isinstance(x, collections.Iterable)
мне понятнее, и я знаю Python.C
удаление подкласса (или повреждение без возможности восстановления)abc_method()
унаследованного отMyABC
. Принципиальное отличие заключается в том, что испортить договор наследования является суперкласс, а не подкласс.Container.register(ContainAllTheThings)
чтобы данный пример работал?__subclasshook__
«любой класс, который удовлетворяет этому предикату, считается подклассом для целейisinstance
иissubclass
проверок, независимо от того, был ли он зарегистрирован в ABC и независимо от того, является ли он прямым подклассом ». Как я сказал в ответе, если вы реализуете правильный интерфейс, вы подкласс!Удобной особенностью ABC является то, что если вы не реализуете все необходимые методы (и свойства), вы получите ошибку при создании экземпляра, а не
AttributeError
, возможно, намного позже, когда вы фактически попытаетесь использовать отсутствующий метод.Пример из https://dbader.org/blog/abstract-base-classes-in-python
Изменить: включить синтаксис Python3, спасибо @PandasRocks
источник
Это значительно облегчит определение того, поддерживает ли объект данный протокол, без необходимости проверять наличие всех методов в протоколе или без исключения в глубине «вражеской» территории из-за отсутствия поддержки.
источник
Абстрактный метод должен убедиться, что любой метод, который вы вызываете в родительском классе, должен присутствовать в дочернем классе. Ниже приведены способы вызова noraml и использования тезисов. Программа написана на python3
Нормальный способ звонка
С абстрактным методом
Так как methodtwo не вызывается в дочернем классе, мы получили ошибку. Правильная реализация ниже
источник