@ Oddthinking ответ не является неправильным, но я думаю, что он пропускает real , практическую причину, по которой Python имеет азбуку в мире утиной печати.
Абстрактные методы аккуратны, но, на мой взгляд, они на самом деле не заполняют ни одного сценария использования, еще не охваченного набором утиных символов. Реальная сила абстрактных базовых классов заключается в способе, которым они позволяют настраивать поведение isinstance
и issubclass
. (__subclasshook__
- это, по сути, более дружественный API поверх хуков Python __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
Другими словами, если вы реализуете правильный интерфейс, вы подкласс! 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
.
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
К сожалению, это одна из тех ловушек «просто не делай этого» (которых у Python относительно мало!): Избегайте определения ABC как __subclasshook__
, так и неабстрактными методами. Более того, вы должны привести свое определение __subclasshook__
в соответствие с набором абстрактных методов, которые определяет ваша ABC.