Різниця між абстрактним класом та інтерфейсом в Python


Відповіді:


618

Що ви побачите іноді, це наступне:

class Abstract1( object ):
    """Some description that tells you it's abstract,
    often listing the methods you're expected to supply."""
    def aMethod( self ):
        raise NotImplementedError( "Should have implemented this" )

Оскільки Python не має (і не потребує) формального контракту на інтерфейс, різниці між абстракцією та інтерфейсом у Java не існує. Якщо хтось докладе зусиль для визначення формального інтерфейсу, це також буде абстрактним класом. Єдині відмінності полягали б у заявленому намірі в docstring.

А різниця між абстрактним та інтерфейсом - це різання волосся, коли ви набираєте качку.

Java використовує інтерфейси, оскільки не має багаторазового успадкування.

Оскільки Python має багаторазове успадкування, ви також можете побачити щось подібне

class SomeAbstraction( object ):
    pass # lots of stuff - but missing something

class Mixin1( object ):
    def something( self ):
        pass # one implementation

class Mixin2( object ):
    def something( self ):
        pass # another

class Concrete1( SomeAbstraction, Mixin1 ):
    pass

class Concrete2( SomeAbstraction, Mixin2 ):
    pass

При цьому використовується своєрідний абстрактний надклас з міксинами для створення конкретних підкласів, які є непересічними.


5
С. Лотт, ви маєте на увазі, що через введення качки розмежування між has-a (інтерфейс) та is-a (успадкування) не є суттєвим?
Лоренцо

3
Різниця між абстрактним та інтерфейсом - це різання волосся, коли ви набираєте качку. Я не знаю, що означає "суттєве". Це "справжнє" - воно має суть - з точки зору дизайну. Але з мовної точки зору може не бути підтримки. Ви можете прийняти конвенції, щоб розрізняти абстрактний клас та визначення класу інтерфейсу в Python.
S.Lott

26
@ L.DeLeo - ви впевнені, що ваше поняття has-a vs. is-a правильне? Я зазвичай розглядаю розмежування як змінну has-a = member порівняно з is-a = наслідування (батьківський клас або інтерфейс). Подумайте порівняно або перелічіть у Java; це є відносини, незалежно від того, чи це інтерфейси чи абстрактні класи.
dimo414

43
NotImplementedError("Class %s doesn't implement aMethod()" % (self.__class__.__name__))є більш інформативним повідомлення про помилку :)
naught101

9
@Lorenzo відносини не мають нічого спільного з успадкуванням, типом качок, інтерфейсами та абстрактними класами (усі чотири стосуються «відносин»).
Карл Ріхтер

196

Яка різниця між абстрактним класом та інтерфейсом у Python?

Інтерфейс для об'єкта - це набір методів та атрибутів на цьому об'єкті.

У Python ми можемо використовувати абстрактний базовий клас для визначення та застосування інтерфейсу.

Використання абстрактного базового класу

Наприклад, скажімо, що ми хочемо використовувати один з абстрактних базових класів з collectionsмодуля:

import collections
class MySet(collections.Set):
    pass

Якщо ми спробуємо використати його, отримаємо те, TypeErrorщо створений нами клас не підтримує очікувану поведінку множин:

>>> MySet()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MySet with abstract methods
__contains__, __iter__, __len__

Таким чином , ми повинні реалізувати по крайней мере __contains__ , __iter__і __len__. Давайте використаємо цей приклад реалізації з документації :

class ListBasedSet(collections.Set):
    """Alternate set implementation favoring space over speed
    and not requiring the set elements to be hashable. 
    """
    def __init__(self, iterable):
        self.elements = lst = []
        for value in iterable:
            if value not in lst:
                lst.append(value)
    def __iter__(self):
        return iter(self.elements)
    def __contains__(self, value):
        return value in self.elements
    def __len__(self):
        return len(self.elements)

s1 = ListBasedSet('abcdef')
s2 = ListBasedSet('defghi')
overlap = s1 & s2

Реалізація: Створення абстрактного базового класу

Ми можемо створити власний абстрактний базовий клас, встановивши метаклас abc.ABCMetaта використовуючи abc.abstractmethodдекоратор на відповідні методи. Метаклас додасть __abstractmethods__атрибути оформлених функцій, не дозволяючи створювати екземпляри до тих пір, поки вони не будуть визначені.

import abc

Наприклад, "effable" визначається як щось, що можна виразити словами. Скажіть, що ми хотіли б визначити абстрактний базовий клас, який можна додати, в Python 2:

class Effable(object):
    __metaclass__ = abc.ABCMeta
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

Або в Python 3, з незначною зміною декларування метакласу:

class Effable(object, metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

Тепер, якщо ми спробуємо створити об'єкт, який можна зробити, не реалізуючи інтерфейс:

class MyEffable(Effable): 
    pass

і спробувати інстанціювати:

>>> MyEffable()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyEffable with abstract methods __str__

Нам кажуть, що ми ще не закінчили роботу.

Тепер, якщо ми дотримуємося, надаючи очікуваний інтерфейс:

class MyEffable(Effable): 
    def __str__(self):
        return 'expressable!'

Тоді ми можемо використовувати конкретну версію класу, похідну від абстрактного:

>>> me = MyEffable()
>>> print(me)
expressable!

Ми можемо зробити з цим інші речі, як, наприклад, зареєструвати віртуальні підкласи, які вже реалізують ці інтерфейси, але я думаю, що це виходить за рамки цього питання. Інші методи, продемонстровані тут, повинні були адаптувати цей метод, використовуючи abcмодуль для цього.

Висновок

Ми продемонстрували, що створення абстрактного базового класу визначає інтерфейси для користувацьких об'єктів у Python.


101

У Python> = 2.6 є абстрактні базові класи .

Анотація базових класів (скорочено ABC) доповнює типізацію качок, надаючи спосіб визначення інтерфейсів, коли інші методи, такі як hasattr (), будуть незграбними. Python оснащений багатьма вбудованими АВС для структур даних (у модулі колекції), чисел (у модулі чисел) та потоків (у модулі io). Ви можете створити свій власний ABC за допомогою модуля abc.

Існує також модуль Zope Interface , який використовується проектами поза зоною, як виті. Я не дуже знайомий з ним, але є вікі сторінка тут , що може допомогти.

Взагалі вам не потрібна концепція абстрактних класів або інтерфейсів у python (відредаговано - детальну інформацію див. У відповіді С.Лотта).


2
Що ви отримуєте, використовуючи ABC в Python?
CpILL

38

У Python насправді немає жодної концепції.

Він використовує типи качок, які усунули потребу в інтерфейсах (принаймні для комп'ютера :-))

Python <= 2.5: Базові класи, очевидно, існують, але явного способу позначити метод як "чистий віртуальний" немає, тому клас насправді не є абстрактним.

Python> = 2.6: Абстрактні базові класи існують ( http://docs.python.org/library/abc.html ). І дозволяють вказати методи, які необхідно реалізувати в підкласах. Синтаксис мені не дуже подобається, але особливість є. У більшості випадків, мабуть, краще використовувати набирання качок з боку клієнта, що «використовує».


3
Python 3.0 додає реальні абстрактні базові класи. Вони використовуються в модулі колекції, а також в інших місцях. docs.python.org/3.0/library/abc.html
Лара Дуган

Довідка про те, чому типінг качок усуває потребу в інтерфейсах, буде корисною. Мені не здається очевидним, що введення качки, яке я розумію як здатність «ткнути» будь-який метод або атрибут на будь-який об’єкт, означало б, що вам не потрібно вказувати необхідні поведінки (і змусити компілятора нагадати вам реалізувати їх), саме так я розумію абстрактні базові класи.
Reb.Cabin

Це менше качки, а потім підтримка багаторазового успадкування, яка видаляє штучну лінію між інтерфейсом та абстрактним класом, наприклад, на Java.
Мазі

35

Більш простим способом пояснити: Інтерфейс схожий на порожню каструлю для булочок. Це файл класу з набором визначень методів, які не мають коду.

Абстрактний клас - це те саме, але не всі функції повинні бути порожніми. Деякі можуть мати код. Це не суворо порожньо.

Чому розрізняти: Існує не так багато практичної різниці в Python, але на рівні планування для великого проекту може бути звичайніше говорити про інтерфейси, оскільки немає коду. Особливо, якщо ви працюєте з програмістами Java, які звикли до цього терміну.


+1 за відмінність, де ABC може мати власні реалізації - що здається дуже класним способом перехитрити себе
Titou

17

Взагалі інтерфейси використовуються лише мовами, що використовують модель класу з єдиним успадкуванням. У цих мовах єдиного успадкування зазвичай використовуються інтерфейси, якщо будь-який клас міг би використовувати певний метод або набір методів. Також у цих мовах одного успадкування абстрактні класи використовуються для того, щоб або мати визначені змінні класу на додаток до жодних або більше методів, або використовувати модель єдиного успадкування для обмеження діапазону класів, які могли використовувати набір методів.

Мови, що підтримують модель множинного успадкування, як правило, використовують лише класи або абстрактні базові класи, а не інтерфейси. Оскільки Python підтримує багатократне успадкування, він не використовує інтерфейси, і ви хочете використовувати базові класи або абстрактні базові класи.

http://docs.python.org/library/abc.html


2

Абстрактні заняття - це заняття, які містять один або кілька абстрактних методів. Поряд з абстрактними методами, Абстрактні класи можуть мати статичні, класичні та екземплярні методи. Але у випадку інтерфейсу він матиме лише абстрактні методи, не інші. Отже, не обов'язково успадковувати абстрактний клас, але воно є обов'язковим для успадкування інтерфейсу.


1

Для повноти слід згадати PEP3119, де ABC був введений та порівняний з інтерфейсами, та оригінальні талінські коментар .

Абстрактний клас не є ідеальним інтерфейсом:

  • належить до ієрархії спадкування
  • є змінним

Але якщо ви вирішили написати це по-своєму:

def some_function(self):
     raise NotImplementedError()

interface = type(
    'your_interface', (object,),
    {'extra_func': some_function,
     '__slots__': ['extra_func', ...]
     ...
     '__instancecheck__': your_instance_checker,
     '__subclasscheck__': your_subclass_checker
     ...
    }
)

ok, rather as a class
or as a metaclass
and fighting with python to achieve the immutable object
and doing refactoring
...

ви досить швидко зрозумієте, що вигадуєте колесо, щоб врешті досягти abc.ABCMeta

abc.ABCMeta було запропоновано як корисне доповнення відсутньої функціональності інтерфейсу, і це досить справедливо на такій мові, як python.

Звичайно, це вдалося покращити краще під час написання версії 3 та додавання нового синтаксису та незмінної концепції інтерфейсу ...

Висновок:

The abc.ABCMeta IS "pythonic" interface in python
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.