Jak zaimplementować interfejsy w Pythonie?

182
public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

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

    #endregion
}

Jak zaimplementować w języku Python odpowiednik tego kodu 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!'

Czy to dobry pomysł? Podaj przykłady w swoich odpowiedziach.

Pratik Deoghare
źródło
Jaki byłby cel używania interfejsu w twoim przypadku?
Bandi-T
23
Szczerze mówiąc, w ogóle nie ma sensu! Chcę tylko dowiedzieć się, co robić, gdy potrzebujesz interfejsów w Pythonie?
Pratik Deoghare
18
raise NotImplementedErrorjest tym show, czym powinno być ciało - nie ma sensu podnosić całkowicie ogólnego, Exceptiongdy Python definiuje doskonale specyficzny wbudowany! -)
Alex Martelli
2
Czy init nie powinien wywoływać show () w IInterface (lub sam zgłaszać wyjątek), aby nie można było tworzyć abstrakcyjnego interfejsu?
Katastic Voyage
1
Widzę, jak to się przydaje ... powiedzmy, że masz obiekt, który chcesz mieć, ma konkretny podpis. W przypadku pisania kaczego nie można zagwarantować, że obiekt będzie miał oczekiwany podpis. Czasami przydatne może być wymuszenie pisania na właściwościach dynamicznie wpisywanych.
Prime By Design

Odpowiedzi:

150

Jak wspomniano tutaj:

Interfejsy nie są konieczne w Pythonie. Wynika to z faktu, że Python ma odpowiednie wielokrotne dziedziczenie, a także unikanie, co oznacza, że ​​w miejscach, w których musisz mieć interfejsy w Javie, nie musisz ich mieć w Pythonie.

To powiedziawszy, wciąż istnieje kilka zastosowań interfejsów. Niektóre z nich są objęte Pythons Abstract Base Classes, wprowadzonymi w Pythonie 2.6. Są one przydatne, jeśli chcesz tworzyć klasy podstawowe, których nie można utworzyć instancji, ale zapewniają określony interfejs lub część implementacji.

Innym zastosowaniem jest, jeśli chcesz w jakiś sposób określić, że obiekt implementuje określony interfejs, i możesz również użyć do tego ABC, podklasując je. Innym sposobem jest zope.interface, moduł będący częścią Zope Component Architecture, naprawdę niesamowicie fajnego frameworka komponentów. W tym przypadku nie można podklasować interfejsów, ale zamiast tego oznaczać klasy (lub nawet instancje) jako implementujące interfejs. Można to również wykorzystać do wyszukiwania komponentów z rejestru komponentów. Super fajne!

Lennart Regebro
źródło
11
Czy mógłbyś to rozwinąć? 1. Jak wdrożyć taki interfejs? 2. Jak można go wykorzystać do wyszukiwania komponentów?
geoidesic
42
„Interfejsy nie są potrzebne w Pythonie. Z wyjątkiem, gdy są”.
Baptiste Candellier
8
interfejsy są najczęściej używane do uzyskania przewidywalnego wyniku / egzekwowania poprawności elementów podczas przekazywania obiektów. byłoby wspaniale, gdyby Python obsługiwał to jako opcję. pozwoliłoby również narzędziom programistycznym na lepszą inteligencję
Sonic Soul
1
Przykład znacznie poprawiłby tę odpowiedź.
Bob
5
„To dlatego, że Python ma odpowiednie wielokrotne dziedzictwo”, kto powiedział, że interfejsy są dla wielokrotnego dziedziczenia?
adnanmuttaleb
76

Wydaje się, że użycie modułu abc do abstrakcyjnych klas bazowych jest wystarczające.

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()
Peter Torpman
źródło
11
Tak, potrzebuję modułu do tego, co powinno być częścią samego języka lub w ogóle nie być używane, IMO.
Mike de Klerk
masz na myśli if not server.version() == '1.0': raise ...:? Naprawdę nie rozumiem tej linii. Wyjaśnienie byłoby mile widziane.
Skandix,
1
@MikedeKlerk Nie mogłem się więcej zgodzić. Podobnie jak odpowiedź Python na pisanie; Nie powinienem importować modułu, aby zadeklarować, że chcę, aby typ był typem. Odpowiedź brzmi: „dobrze Python jest dynamicznie typowany”, ale to nie jest wymówka. Java + Groovy rozwiązuje ten problem. Java dla rzeczy statycznych, Groovy dla rzeczy dynamicznych.
ubiquibacon
6
@MikedeKlerk, moduł abc jest rzeczywiście wbudowany w Pythona. Trochę więcej pracy wymaga skonfigurowanie niektórych z tych wzorców, ponieważ są one w większości niepotrzebne w Pythonie ze względu na alternatywne wzorce, które są uważane za „bardziej Pythonic”. Dla zdecydowanej większości programistów byłoby to, jak powiedziałeś, „w ogóle nieużywane”. Jednak, biorąc pod uwagę, że istnieją bardzo niszowe przypadki, które naprawdę wymagają tych możliwości interfejsów, twórcy Pythona udostępnili łatwy w użyciu interfejs API, aby to umożliwić.
David Culbreth,
39

interfejs obsługuje Python 2.7 i Python 3.4+.

Aby zainstalować interfejs, musisz

pip install python-interface

Przykładowy kod:

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
Blue Ray
źródło
7
Główną zaletą tej biblioteki, IMHO, jest wczesne niepowodzenie, które daje: Jeśli twoja klasa nie implementuje poprawnie określonego interfejsu, otrzymasz wyjątek, gdy tylko klasa zostanie wczytana - nawet nie musisz go używać . Dzięki własnej abstrakcyjnej klasie bazowej Python otrzymujesz wyjątek podczas pierwszej instancji swojej klasy, co może być znacznie później.
Hans
To nie jest konieczne, ABC oferuje podobną, wbudowaną funkcjonalność.
Daniel
@DanielCasares czy ABC oferuje rzeczywisty interfejs, czy masz na myśli, że klasy abstrakcyjne bez stanu lub implementacji są rozwiązaniem oferowanym przez ABC?
asaf92
36

Implementacja interfejsów z abstrakcyjnymi klasami podstawowymi jest znacznie prostsza we współczesnym Pythonie 3 i służą one jako umowa interfejsu dla rozszerzeń wtyczek.

Utwórz interfejs / abstrakcyjną klasę podstawową:

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)

Utwórz normalną podklasę i zastąp wszystkie metody abstrakcyjne:

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)

Opcjonalnie możesz mieć wspólną implementację metod abstrakcyjnych jak w create_sale_invoice(), wywołując ją super()jawnie w podklasie, jak wyżej.

Instancja podklasy, która nie implementuje wszystkich metod abstrakcyjnych, kończy się niepowodzeniem:

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

Możesz także mieć właściwości abstrakcyjne, metody statyczne i klasy, łącząc odpowiednie adnotacje z @abstractmethod.

Abstrakcyjne klasy podstawowe świetnie nadają się do wdrażania systemów opartych na wtyczkach. Wszystkie zaimportowane podklasy klasy są dostępne przez __subclasses__(), więc jeśli załadujesz wszystkie klasy z katalogu wtyczek importlib.import_module()i jeśli podklasują one klasę podstawową, masz bezpośredni dostęp do nich przez __subclasses__()i możesz być pewien, że umowa interfejsu jest egzekwowana dla wszystkich je podczas tworzenia.

Oto implementacja ładowania wtyczki dla AccountingSystempowyższego przykładu:

...
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

Następnie możesz uzyskać dostęp do obiektu wtyczki systemu księgowego poprzez AccountingSystemklasę:

>>> accountingsystem = AccountingSystem.instance()

(Zainspirowany tym postem PyMOTW-3 .)

mrts
źródło
Pytanie: Co oznacza nazwa modułu „ABC”?
Sebastian Nielsen
„ABC” oznacza „Abstract Base Classes”, patrz oficjalne dokumenty
mrts
31

Istnieją implementacje interfejsów dla języka Python (najpopularniejsze to Zope , również używane w Twisted ), ale częściej kodery Python wolą używać bogatszej koncepcji znanej jako „Abstract Base Class” (ABC), która łączy interfejs z możliwość posiadania również niektórych aspektów wdrażania. ABC są szczególnie dobrze obsługiwane w Pythonie 2.6 i późniejszych, patrz PEP , ale nawet we wcześniejszych wersjach Pythona są zwykle postrzegane jako „droga do wyjścia” - wystarczy zdefiniować klasę, której niektóre metody wychowują NotImplementedError, aby podklasy były zauważając, że lepiej zastąpią te metody!)

Alex Martelli
źródło
3
Istnieją implementacje interfejsów dla Pythona przez osoby trzecie Co to oznacza? Czy mógłbyś wyjaśnić ABC?
Pratik Deoghare
2
Cóż, będę miał problem z tym, że ABC jest „bogatsza”. ;) Są rzeczy, które zope.interface może zrobić, że ABC nie może tak dobrze, jak na odwrót. Ale poza tym masz jak zwykle rację. +1
Lennart Regebro
1
@Alfred: Oznacza to, że moduły takie jak zope.interface nie są zawarte w standardowej bibliotece, ale są dostępne z pypi.
Lennart Regebro
Nadal mam trudność, by pogodzić się z koncepcją ABC. Czy ktoś mógłby przepisać twistedmatrix.com/documents/current/core/howto/components.html (IMHO, doskonałe wyjaśnienie koncepcji interfejsów) w kategoriach ABC. Czy to ma sens?
mcepl
21

Coś takiego (może nie działać, ponieważ nie mam w pobliżu Pythona):

class IInterface:
    def show(self): raise NotImplementedError

class MyClass(IInterface):
    def show(self): print "Hello World!"
Bandyta
źródło
2
Co mam zrobić __init__(self)z konstruktorem?
Pratik Deoghare
1
Zależy od Ciebie. Ponieważ nie ma sprawdzania czasu kompilacji przed skonstruowaniem obiektu z klasy abstrakcyjnej, nie zyskałbyś żadnej ochrony podczas kodowania / kompilacji. Zostanie odziedziczony konstruktor, więc obiekt zostanie utworzony, po prostu „pusty”. To Ty decydujesz, czy lepiej byś to zrobił, pozwalając na to i łapiąc awarie później, czy też jawnie zatrzymując program od czasu do czasu, implementując podobny konstruktor zgłaszający wyjątek.
Bandi-T
1
Dlatego abc.ABCjest o wiele lepszy niż podnoszenie NotImplementedError- tworzenie instancji abc.ABCpodklasy, która nie implementuje wszystkich metod abstrakcyjnych, kończy się wcześnie, więc jesteś chroniony przed błędami. Zobacz moją odpowiedź poniżej, aby zobaczyć, jak wygląda błąd.
mrts
8

Rozumiem, że interfejsy nie są tak potrzebne w dynamicznych językach, takich jak Python. W Javie (lub C ++ z abstrakcyjną klasą podstawową) interfejsy są sposobem na zapewnienie, że np. Przekazujesz odpowiedni parametr, zdolny do wykonania zestawu zadań.

Np. Jeśli masz obserwatora i jest obserwowalny, obserwowalny jest zainteresowany subskrybowaniem obiektów obsługujących interfejs IObserver, który z kolei ma notifyakcję. Jest to sprawdzane podczas kompilacji.

W Pythonie nie ma czegoś takiego, compile timea wyszukiwanie metod odbywa się w czasie wykonywania. Ponadto można zastąpić wyszukiwanie za pomocą magicznych metod __getattr __ () lub __getattribute __ (). Innymi słowy, możesz przekazać, jako obserwator, dowolny obiekt, który może zwrócić wywoływalne żądanie dostępu notify.

To prowadzi mnie do wniosku, że interfejsy w Pythonie istnieją - po prostu ich egzekwowanie jest odroczone do momentu, w którym są faktycznie używane

Tomasz Zieliński
źródło