Как мне реализовать интерфейсы в Python?

182
public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

    public void show()
    {
        Console.WriteLine("Hello World!");
    }

    #endregion
}

Как мне реализовать Python-эквивалент этого кода на C #?

class IInterface(object):
    def __init__(self):
        pass

    def show(self):
        raise Exception("NotImplementedException")


class MyClass(IInterface):
   def __init__(self):
       IInterface.__init__(self)

   def show(self):
       print 'Hello World!'

Это хорошая идея?? Пожалуйста, приведите примеры в своих ответах.

Пратик Деогхаре
источник
Какова цель использования интерфейса в вашем случае?
Bandi-T
23
Откровенно говоря, нет цели вообще! Я просто хочу узнать, что делать, когда вам нужны интерфейсы в Python?
Pratik Deoghare
18
raise NotImplementedErrorэто то, что showтело должно быть - не имеет смысла поднимать полностью универсальный, Exceptionкогда Python определяет совершенно конкретный встроенный! -)
Алекс Мартелли
2
Не должен ли init вызывать show () в IInterface (или вызывать само исключение), чтобы вы не могли создать экземпляр абстрактного интерфейса?
Katastic Voyage
1
Я вижу некоторую пользу для этого ... допустим, у вас есть объект, который вы хотите гарантировать, имеет определенную подпись. При вводе утки вы не можете гарантировать, что объект будет иметь ожидаемую подпись. Иногда может быть полезно принудительно ввести некоторые свойства для динамически типизированных свойств.
Prime By Design

Ответы:

151

Как уже упоминалось другим здесь:

Интерфейсы не нужны в Python. Это потому, что Python имеет правильное множественное наследование, а также ducktyping, что означает, что в тех местах, где вы должны иметь интерфейсы в Java, вам не нужно иметь их в Python.

Тем не менее, есть еще несколько вариантов использования интерфейсов. Некоторые из них охватываются абстрактными базовыми классами Pythons, представленными в Python 2.6. Они полезны, если вы хотите создать базовые классы, которые не могут быть созданы, но предоставляют определенный интерфейс или часть реализации.

Другое использование, если вы как-то хотите указать, что объект реализует определенный интерфейс, и вы можете использовать ABC для этого тоже, создав подклассы из них. Другой способ - это zope.interface, модуль, который является частью архитектуры компонентов Zope, действительно классная структура компонентов. Здесь вы не наследуете интерфейсы, а вместо этого помечаете классы (или даже экземпляры) как реализующие интерфейс. Это также может быть использовано для поиска компонентов в реестре компонентов. Очень круто!

Леннарт Регебро
источник
11
Не могли бы вы уточнить это? 1. Как реализовать такой интерфейс? 2. Как это можно использовать для поиска компонентов?
геоидезия
44
«Интерфейсы не нужны в Python. За исключением случаев, когда они есть».
Батист Канделлье
8
интерфейсы в основном используются для обеспечения предсказуемого результата / обеспечения правильности членов при передаче объектов. было бы замечательно, если бы Python поддерживал это в качестве опции. это также позволило бы инструментам разработки иметь лучший смысл
Sonic Soul
1
Пример мог бы значительно улучшить этот ответ.
Боб
5
«Это потому, что у Python правильное множественное наследование», кто сказал, что интерфейсы предназначены для множественного наследования?
adnanmuttaleb
76

Использование модуля abc для абстрактных базовых классов, кажется, делает свое дело.

from abc import ABCMeta, abstractmethod

class IInterface:
    __metaclass__ = ABCMeta

    @classmethod
    def version(self): return "1.0"
    @abstractmethod
    def show(self): raise NotImplementedError

class MyServer(IInterface):
    def show(self):
        print 'Hello, World 2!'

class MyBadServer(object):
    def show(self):
        print 'Damn you, world!'


class MyClient(object):

    def __init__(self, server):
        if not isinstance(server, IInterface): raise Exception('Bad interface')
        if not IInterface.version() == '1.0': raise Exception('Bad revision')

        self._server = server


    def client_show(self):
        self._server.show()


# This call will fail with an exception
try:
    x = MyClient(MyBadServer)
except Exception as exc:
    print 'Failed as it should!'

# This will pass with glory
MyClient(MyServer()).client_show()
Питер Торпман
источник
11
Югх, нужен какой-то модуль для того, что должно быть частью самого языка или вообще не использоваться, IMO.
Майк де Клерк
Вы имеете в виду: if not server.version() == '1.0': raise ...? Я действительно не понимаю эту линию. Объяснение будет приветствоваться.
Skandix
1
@MikedeKlerk Я не могу не согласиться. Так же, как ответ Python на набор текста; Мне не нужно импортировать модуль, чтобы объявить, что я хочу, чтобы тип был типом. Ответ на это, как правило, «хорошо, что Python динамически типизирован», но это не оправдание. Java + Groovy решит эту проблему. Java для статических вещей, Groovy для динамических вещей.
убиквибакон
6
@MikedeKlerk, модуль abc действительно встроен в python. Настроить некоторые из этих шаблонов немного больше, потому что они в основном не нужны в Python из-за альтернативных шаблонов, которые считаются «более Pythonic». Для подавляющего большинства разработчиков, как вы сказали, «вообще не используется». Однако, признавая, что есть некоторые очень нишевые случаи, которые действительно требуют этих возможностей взаимодействия, создатели Python предоставили простой в использовании API, чтобы сделать это возможным.
Дэвид
39

Интерфейс поддерживает Python 2.7 и Python 3.4+.

Для установки интерфейса вам необходимо

pip install python-interface

Пример кода:

from interface import implements, Interface

class MyInterface(Interface):

    def method1(self, x):
        pass

    def method2(self, x, y):
        pass


class MyClass(implements(MyInterface)):

    def method1(self, x):
        return x * 2

    def method2(self, x, y):
        return x + y
блюрэй
источник
7
IMHO, основным преимуществом этой библиотеки является ранний сбой, который она дает вам: если ваш класс неправильно реализует указанный интерфейс, вы получите исключение, как только класс будет прочитан - вам даже не придется его использовать , С собственным абстрактным базовым классом Python вы получаете исключение при первом создании экземпляра вашего класса, что может быть гораздо позже.
Ганс
Это не нужно, ABC предлагает аналогичные встроенные функции.
Даниил
@DanielCasares предлагает ли ABC реальный интерфейс или вы имеете в виду, что абстрактные классы без состояний или реализаций являются решением, которое предлагает ABC?
asaf92
36

Реализация интерфейсов с абстрактными базовыми классами намного проще в современном Python 3, и они служат в качестве контракта интерфейса для расширений плагинов.

Создайте интерфейс / абстрактный базовый класс:

from abc import ABC, abstractmethod

class AccountingSystem(ABC):

    @abstractmethod
    def create_purchase_invoice(self, purchase):
        pass

    @abstractmethod
    def create_sale_invoice(self, sale):
        log.debug('Creating sale invoice', sale)

Создайте нормальный подкласс и переопределите все абстрактные методы:

class GizmoAccountingSystem(AccountingSystem):

    def create_purchase_invoice(self, purchase):
        submit_to_gizmo_purchase_service(purchase)

    def create_sale_invoice(self, sale):
        super().create_sale_invoice(sale)
        submit_to_gizmo_sale_service(sale)

При желании вы можете иметь общую реализацию в абстрактных методах, например create_sale_invoice(), вызывая ее super()явно в подклассе, как указано выше.

Создание экземпляра подкласса, который не реализует все абстрактные методы, завершается неудачей:

class IncompleteAccountingSystem(AccountingSystem):
    pass

>>> accounting = IncompleteAccountingSystem()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class IncompleteAccountingSystem with abstract methods
create_purchase_invoice, create_sale_invoice

Вы также можете иметь абстрактные свойства, статические и классовые методы, комбинируя соответствующие аннотации с @abstractmethod.

Абстрактные базовые классы отлично подходят для реализации систем на основе плагинов. Все импортированные подклассы класса доступны через __subclasses__(), поэтому, если вы загружаете все классы из каталога плагинов с помощью importlib.import_module()и если они подклассируют базовый класс, у вас есть прямой доступ к ним через __subclasses__()и вы можете быть уверены, что контракт интерфейса применяется для всех их во время реализации.

Вот реализация загрузки плагина для AccountingSystemприведенного выше примера:

...
from importlib import import_module

class AccountingSystem(ABC):

    ...
    _instance = None

    @classmethod
    def instance(cls):
        if not cls._instance:
            module_name = settings.ACCOUNTING_SYSTEM_MODULE_NAME
            import_module(module_name)
            subclasses = cls.__subclasses__()
            if len(subclasses) > 1:
                raise InvalidAccountingSystemError('More than one '
                        f'accounting module: {subclasses}')
            if not subclasses or module_name not in str(subclasses[0]):
                raise InvalidAccountingSystemError('Accounting module '
                        f'{module_name} does not exist or does not '
                        'subclass AccountingSystem')
            cls._instance = subclasses[0]()
        return cls._instance

Затем вы можете получить доступ к объекту плагина системы учета через AccountingSystemкласс:

>>> accountingsystem = AccountingSystem.instance()

(Вдохновлено этим сообщением PyMOTW-3 .)

MRTS
источник
Вопрос: Что означает название модуля "ABC"?
Себастьян Нильсен
«ABC» расшифровывается как «Абстрактные базовые классы», см. Официальные документы
mrts
31

Существуют сторонние реализации интерфейсов для Python (наиболее популярными являются Zope , также используемые в Twisted ), но чаще всего программисты Python предпочитают использовать более богатую концепцию, известную как «Абстрактный базовый класс» (ABC), которая сочетает интерфейс с возможность иметь некоторые аспекты реализации там тоже. ABC особенно хорошо поддерживаются в Python 2.6 и более поздних версиях, см. PEP , но даже в более ранних версиях Python они обычно рассматриваются как «путь» - просто определите класс, некоторые из методов которого возникают, NotImplementedErrorчтобы подклассы были заметьте, что им лучше переопределить эти методы! -)

Алекс Мартелли
источник
3
Существуют сторонние реализации интерфейсов для Python Что это значит? Не могли бы вы объяснить ABC?
Pratik Deoghare
2
Ну, я возьму вопрос о том, что ABC "богаче". ;) Есть вещи, которые zope.interface может сделать, которые ABC не может сделать так же, как и наоборот. Но в остальном ты как всегда прав. +1
Леннарт Регебро
1
@Alfred: это означает, что такие модули, как zope.interface, не включены в стандартную библиотеку, но доступны из pypi.
Леннарт Регебро
Мне все еще трудно понять концепцию азбуки. Может ли кто-нибудь переписать twistedmatrix.com/documents/current/core/howto/components.html (IMHO, отличное объяснение концепции интерфейсов) в терминах ABC. Это имеет смысл?
Mcepl
21

Примерно так (может не работать, так как у меня нет Python):

class IInterface:
    def show(self): raise NotImplementedError

class MyClass(IInterface):
    def show(self): print "Hello World!"
Банди-Т
источник
2
Что я должен делать __init__(self)с конструктором?
Pratik Deoghare
1
Вам решать. Поскольку во время компиляции нет проверки на создание объекта из абстрактного класса, вы не получите никакой защиты во время кодирования / компиляции. Будет наследоваться конструктор, поэтому объект будет создан, просто будет «пустым». Вам решать, будет ли вам лучше, если вы позволите этому случиться, и позже обнаружите сбои, или явно остановите программу прямо сейчас, реализовав аналогичный конструктор, выдавший исключение.
Bandi-T
1
Вот почему abc.ABCэто намного лучше, чем повышение NotImplementedError- создание экземпляра abc.ABCподкласса, который не реализует все абстрактные методы, рано или поздно завершается неудачей, поэтому вы защищены от ошибок. Смотрите мой ответ ниже, чтобы узнать, как выглядит ошибка.
Мртс
8

Насколько я понимаю, интерфейсы не нужны в динамических языках, таких как Python. В Java (или C ++ с его абстрактным базовым классом) интерфейсы являются средством для обеспечения того, что, например, вы передаете правильный параметр, способный выполнять множество задач.

Например, если у вас есть наблюдатель и наблюдаемый, наблюдаемый заинтересован в подписке объектов, которые поддерживают интерфейс IObserver, который, в свою очередь, имеет notifyдействие. Это проверяется во время компиляции.

В Python нет такого понятия, как compile time поиск методов выполняется во время выполнения. Кроме того, можно переопределить поиск с помощью магических методов __getattr __ () или __getattribute __ (). Другими словами, вы можете передать в качестве наблюдателя любой объект, который может вернуть вызываемый при доступе к notifyатрибуту.

Это приводит меня к выводу, что интерфейсы в Python существуют - просто их применение откладывается до момента, когда они фактически используются.

Томаш Зелиньски
источник