Зачем использовать абстрактные базовые классы в Python?
поскольку я привык к старым способам ввода утки в Python, я не понимаю необходимости ABC (абстрактные базовые классы). Элемент помогите хорошо о том, как их использовать.
Я попытался прочитать обоснование в бодрость духа, но это было выше моей головы. Если бы я искал контейнер изменяемой последовательности, я бы проверил __setitem__
, или, скорее всего, попытаться использовать его (EAFP). Я не сталкивался с реальной жизнью использования для цифры модуль, который использует азбуку, но это самое близкое, что я могу понять.
может кто-нибудь объясните мне, пожалуйста?
5 ответов:
короткая версия
Азбука предлагает более высокий уровень семантического контракта между клиентами и реализованными классами.
текст
существует контракт между классом и его абонентами. Класс обещает делать определенные вещи и иметь определенные свойства.
существуют различные уровни контракта.
на очень низком уровне контракт может включать имя метода или его количество параметров.
в статически типизированном языке этот контракт фактически будет выполняться компилятором. В Python вы можете использовать EAFP или интроспекцию, чтобы подтвердить, что неизвестный объект соответствует этому ожидаемому контракту.
но в контракте есть и семантические обещания более высокого уровня.
например, если есть
__str__()
метод, он должен возвращать строковое представление объекта. Это может удалить все содержимое объекта, фиксации транзакция и выплюнуть пустую страницу из принтера... но есть общее понимание того, что он должен делать, описанное в руководстве Python.это частный случай, где семантический контракт описан в руководстве. Что должно быть
print()
метод do? Должен ли он записать объект на принтер или строку на экран или что-то еще? Это зависит - вам нужно прочитать комментарии, чтобы понять полный контракт здесь. Кусок клиентского кода, который просто проверяет, чтоprint()
метод существует подтвердил часть контракта - что вызов метода может быть сделан, но не то, что есть согласие на семантике более высокого уровня вызова.определение абстрактного базового класса (ABC) - это способ создания контракта между исполнителями класса и вызывающими объектами. Это не просто список имен методов, но общее понимание того, что эти методы должны делать. Если вы наследуете от этого ABC, вы обещаете следовать всем правилам, описанным в комментарии, включая семантику
print()
метод.утиная типизация Python имеет много преимуществ в гибкости по сравнению со статической типизацией, но она не решает всех проблем. Азбука предлагает промежуточное решение между свободной формой Python и связью и дисциплиной статически типизированного языка.
@ Oddthinking ответ не ошибается, но я думаю, что он пропускает реальные,практические причина Python имеет азбуку в мире утиной печати.
абстрактные методы аккуратны, но, на мой взгляд, они действительно не заполняют никаких прецедентов, которые еще не охвачены утиной типизацией. Реальная сила абстрактных базовых классов заключается в то, как они позволяют настроить поведение
isinstance
иissubclass
. (__subclasshook__
в основном более дружелюбный API поверх Питона__instancecheck__
и__subclasscheck__
крючки.) Адаптация встроенных конструкций для работы с пользовательскими типами является очень важной частью философии Python.исходный код Python является образцовым. здесь как
collections.Container
определен в стандартной библиотеке (на момент написания):class Container(metaclass=ABCMeta): __slots__ = () @abstractmethod def __contains__(self, x): return False @classmethod def __subclasshook__(cls, C): if cls is Container: if any("__contains__" in B.__dict__ for B in C.__mro__): return True return NotImplemented
определение
__subclasshook__
говорит, что любой класс__contains__
атрибут считается подклассом контейнера, даже если он не подкласс его непосредственно. Так Что Я можно написать так:class ContainAllTheThings(object): def __contains__(self, item): return True >>> issubclass(ContainAllTheThings, collections.Container) True >>> isinstance(ContainAllTheThings(), collections.Container) True
другими словами, если вы реализуете интерфейс, вы подклассе! Азбука обеспечивает формальный способ определения интерфейсов в Python,оставаясь верным духу утиного ввода. Кроме того, это работает таким образом, что чтит Принцип Открытости-Закрытости.
объектная модель Python внешне похожа на более "традиционную" систему OO (под которой я подразумеваю Java*) - у нас есть свои классы, yer объекты, ваши методы-но когда вы поцарапаете поверхность, вы найдете что-то гораздо более богатое и гибкое. Аналогично, понятие абстрактных базовых классов Python может быть распознано разработчиком Java, но на практике они предназначены для совсем другой цели.
иногда я пишу полиморфные функции, которые могут действовать на один элемент или коллекцию элементов, и я нахожу
isinstance(x, collections.Iterable)
чтобы быть гораздо более читаемым, чемhasattr(x, '__iter__')
или эквивалентtry...except
блок. (Если вы не знал Python, который из этих трех сделает намерение кода наиболее ясным?)я нахожу, что мне редко нужно писать свою собственную ABC - я предпочитаю полагаться на duck typing - и я обычно обнаруживаю необходимость в ней через рефакторинг. Если я вижу полиморфную функцию, выполняющую множество проверок атрибутов, или множество функций, выполняющих одни и те же проверки атрибутов, этот запах предполагает существование ABC, ожидающего извлечения.
*не вдаваясь в дебаты о том, является ли Java "традиционной" системой OO...
дополнительное соглашение: даже если абстрактный базовый класс может переопределить поведение
isinstance
иissubclass
, он все еще не входит в MRO виртуальный подкласс. Это потенциальная ловушка для клиентов: не каждый объект, для которогоisinstance(x, MyABC) == True
есть методы, определенные наMyABC
.class MyABC(metaclass=abc.ABCMeta): def abc_method(self): pass @classmethod def __subclasshook__(cls, C): return True class C(object): pass # typical client code c = C() if isinstance(c, MyABC): # will be true c.abc_method() # raises AttributeError
к сожалению, это одна из тех ловушек" просто не делайте этого " (из которых Питон имеет относительно мало!): не определять Азбука с
__subclasshook__
и неабстрактные методы. Кроме того, вы должны сделать свое определение__subclasshook__
соответствует набору абстрактных методов, которые определяет ваш ABC.
удобная функция ABCs заключается в том, что если вы не реализуете все необходимые методы (и свойства), вы получаете ошибку при создании экземпляра, а не
AttributeError
, потенциально гораздо позже, когда вы на самом деле пытаетесь использовать отсутствующий метод.from abc import ABCMeta, abstractmethod # python2 class Base(object): __metaclass__ = ABCMeta @abstractmethod def foo(self): pass @abstractmethod def bar(self): pass # python3 class Base(object, metaclass=ABCMeta): @abstractmethod def foo(self): pass @abstractmethod def bar(self): pass class Concrete(Base): def foo(self): pass # We forget to declare `bar` c = Concrete() # TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"
пример из https://dbader.org/blog/abstract-base-classes-in-python
Edit: чтобы включить синтаксис python3, спасибо @PandasRocks
Это позволит определить, поддерживает ли объект данный протокол без необходимости проверки наличия всех методов в протоколе или без запуска исключения глубоко на "вражеской" территории из-за отсутствия поддержки намного проще.
абстрактный метод убедитесь, что любой метод, который вы вызываете в родительском классе, должен отображаться в дочернем классе. Ниже приведены noraml способ вызова и использования абстрактного. Программа написана на python3
нормальный способ вызова
class Parent: def methodone(self): raise NotImplemented() def methodtwo(self): raise NotImplementedError() class Son(Parent): def methodone(self): return 'methodone() is called' c = Son() c.methodone()
'methodone () называется'
c.methodtwo()
NotImplementedError
С Абстрактными метод
from abc import ABCMeta, abstractmethod class Parent(metaclass=ABCMeta): @abstractmethod def methodone(self): raise NotImplementedError() @abstractmethod def methodtwo(self): raise NotImplementedError() class Son(Parent): def methodone(self): return 'methodone() is called' c = Son()
TypeError: не удается создать экземпляр абстрактного класса Son с абстрактными методами methodtwo.
поскольку methodtwo не вызывается в дочернем классе, мы получили ошибку. Правильная реализация находится ниже
from abc import ABCMeta, abstractmethod class Parent(metaclass=ABCMeta): @abstractmethod def methodone(self): raise NotImplementedError() @abstractmethod def methodtwo(self): raise NotImplementedError() class Son(Parent): def methodone(self): return 'methodone() is called' def methodtwo(self): return 'methodtwo() is called' c = Son() c.methodone()
'methodone () называется'