Разница между абстрактным классом и интерфейсом в 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 между абстракцией и интерфейсом не существует. Если кто-то попытается определить формальный интерфейс, он также будет абстрактным классом. Единственные различия будут в заявленном намерении в строке документации.

И разница между абстрактным и интерфейсным интерфейсом - это потрясающая вещь, когда у вас есть утка.

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 = member и is-a = наследование (родительский класс или интерфейс). Подумайте о сопоставимости или список в Java; это отношения is-a, независимо от того, являются ли они интерфейсами или абстрактными классами.
dimo414
43
NotImplementedError("Class %s doesn't implement aMethod()" % (self.__class__.__name__))более информативно сообщение об ошибке :)
naught101
9
@Lorenzo отношение has-a не имеет ничего общего с наследованием, типизацией утки, интерфейсами и абстрактными классами (все четыре относятся к отношениям is-a).
Карл Рихтер
197

В чем разница между абстрактным классом и интерфейсом в 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

Например, «выполнимый» определяется как нечто, что может быть выражено словами. Скажем, мы хотели определить абстрактный базовый класс, который является выполнимым, в 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 поставляется со многими встроенными ABC для структур данных (в модуле коллекций), чисел (в модуле чисел) и потоков (в модуле io). Вы можете создать свой собственный ABC с помощью модуля abc.

Существует также модуль интерфейса Zope , который используется в проектах вне zope, например, витой. Я не очень знаком с ним, но есть вики страница здесь , что может помочь.

В общем, вам не нужна концепция абстрактных классов или интерфейсов в python (отредактировано - подробности см. В ответе S.Lott).

JimB
источник
2
Что вы получаете, используя азбуку в Python?
CpILL
38

Python на самом деле не имеет ни одной концепции.

Он использует Duck Typing, что устраняет необходимость в интерфейсах (по крайней мере, для компьютера :-))

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-программистами, которые привыкли к этому термину.

SilentSteel
источник
+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
Славомир Ленарт
источник