@ Відповідь 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__атрибутом вважається підкласом Container, навіть якщо він не підкласифікує його безпосередньо. Тож я можу це написати:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
Іншими словами, якщо ви реалізуєте правильний інтерфейс, ви підклас! Азбуки забезпечують формальний спосіб визначення інтерфейсів у Python, залишаючись вірним духу типізації качок. Крім того, це працює таким чином, що шанує принцип відкритого закриття .
Об'єктна модель Python виглядає поверхнево подібною до моделі більш "традиційної" OO (під якою я маю на увазі Java *) - у нас є класи yer, yer-об'єкти, yer-методи - але коли ви подряпаєте поверхню, ви знайдете щось набагато багатше і більш гнучким. Так само, поняття 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__. В обох випадках ви можете розірвати договір; немає такої доказової семантики, як у нас у статичній типізації.