@ Відповідь 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__
. В обох випадках ви можете розірвати договір; немає такої доказової семантики, як у нас у статичній типізації.