@ Ответ Oddthinking не является неправильным, но я думаю , что не попадает в реальный , практический разум 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 как a, так __subclasshook__
и неабстрактными методами. Более того, вы должны привести свое определение в __subclasshook__
соответствие с набором абстрактных методов, которые определяет ваша ABC.
__contains__
и классом, который наследуетсяcollections.Container
? В вашем примере в Python всегда было общее понимание__str__
. Реализация__str__
дает те же обещания, что и наследование от некоторого ABC, а затем реализация__str__
. В обоих случаях вы можете нарушить договор; нет доказуемой семантики, такой как у нас в статической типизации.