Wie implementiere ich Schnittstellen in Python?

182
public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

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

    #endregion
}

Wie implementiere ich ein Python-Äquivalent zu diesem C # -Code?

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!'

Ist das eine gute Idee? Bitte geben Sie Beispiele in Ihren Antworten.

Pratik Deoghare
quelle
Was wäre der Zweck für die Verwendung einer Schnittstelle in Ihrem Fall?
Bandi-T
23
Ehrlich gesagt überhaupt kein Zweck! Ich möchte nur lernen, was zu tun ist, wenn Sie Schnittstellen in Python benötigen.
Pratik Deoghare
18
raise NotImplementedErrorist, was showder Körper sein sollte - macht keinen Sinn, ein vollständig generisches zu erheben, Exceptionwenn Python ein perfekt spezifisches eingebautes definiert! -)
Alex Martelli
2
Sollte init nicht show () in IInterface aufrufen (oder die Ausnahme selbst auslösen), damit Sie keine abstrakte Schnittstelle instanziieren können?
Katastic Voyage
1
Ich kann eine Verwendung dafür sehen ... Nehmen wir an, Sie haben ein Objekt, von dem Sie sicherstellen möchten, dass es eine bestimmte Signatur hat. Bei der Eingabe von Enten können Sie nicht garantieren, dass das Objekt die erwartete Signatur aufweist. Manchmal kann es nützlich sein, die Eingabe für dynamisch typisierte Eigenschaften zu erzwingen.
Prime By Design

Antworten:

150

Wie von anderen hier erwähnt:

Schnittstellen sind in Python nicht erforderlich. Dies liegt daran , Python richtige Mehrfachvererbung hat, und auch Duck-Typing, was bedeutet , dass die Orte , wo Sie müssen Schnittstellen in Java haben, können Sie sich nicht in Python haben müssen.

Es gibt jedoch immer noch mehrere Verwendungsmöglichkeiten für Schnittstellen. Einige von ihnen werden von Pythons Abstract Base Classes abgedeckt, die in Python 2.6 eingeführt wurden. Sie sind nützlich, wenn Sie Basisklassen erstellen möchten, die nicht instanziiert werden können, aber eine bestimmte Schnittstelle oder einen Teil einer Implementierung bereitstellen.

Eine andere Verwendung ist, wenn Sie irgendwie angeben möchten, dass ein Objekt eine bestimmte Schnittstelle implementiert, und Sie können ABCs auch dafür verwenden, indem Sie eine Unterklasse von ihnen erstellen. Eine andere Möglichkeit ist zope.interface, ein Modul, das Teil der Zope Component Architecture ist, einem wirklich unglaublich coolen Komponentenframework. Hier werden keine Unterklassen von den Schnittstellen erstellt, sondern Klassen (oder sogar Instanzen) als Implementierung einer Schnittstelle markiert. Dies kann auch verwendet werden, um Komponenten aus einer Komponentenregistrierung nachzuschlagen. Super cool!

Lennart Regebro
quelle
11
Könnten Sie das näher erläutern? 1. Wie implementiert man eine solche Schnittstelle? 2. Wie kann es zum Nachschlagen von Komponenten verwendet werden?
Geoidesic
41
"Schnittstellen sind in Python nicht erforderlich. Außer wenn sie es sind."
Baptiste Candellier
6
Schnittstellen werden meistens verwendet, um ein vorhersehbares Ergebnis zu erzielen / die Korrektheit der Mitglieder beim Weitergeben von Objekten zu erzwingen. Es wäre großartig, wenn Python dies als Option unterstützen würde. es würde auch Entwicklungswerkzeugen ermöglichen, eine bessere Intelligenz zu haben
Sonic Soul
1
Ein Beispiel würde diese Antwort erheblich verbessern.
Bob
3
"Dies liegt daran, dass Python eine ordnungsgemäße Mehrfachvererbung hat", wer hat gesagt, dass Schnittstellen für Mehrfachvererbung vorgesehen sind?
Adnanmuttaleb
76

Die Verwendung des abc-Moduls für abstrakte Basisklassen scheint den Trick zu tun.

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
quelle
11
Yugghh, ich brauche ein Modul für das, was Teil der Sprache selbst sein sollte oder überhaupt nicht verwendet wird, IMO.
Mike de Klerk
meinst du : if not server.version() == '1.0': raise ...? Ich verstehe diese Zeile nicht wirklich. Eine Erklärung wäre willkommen.
Skandix
1
@ MikedeKlerk Ich könnte nicht mehr zustimmen. Genau wie die Python-Antwort auf das Tippen; Ich sollte kein Modul importieren müssen, um zu deklarieren, dass ein Typ ein Typ sein soll. Die Antwort darauf lautet normalerweise "gut, Python ist dynamisch typisiert", aber das ist keine Entschuldigung. Java + Groovy löst dieses Problem. Java für das statische Zeug, Groovy für das dynamische Zeug.
Ubiquibacon
6
@MikedeKlerk, das abc-Modul ist in Python eingebaut. Es ist etwas aufwendiger, einige dieser Muster einzurichten, da sie in Python aufgrund alternativer Muster, die als "pythonischer" gelten, weitgehend nicht benötigt werden. Für die überwiegende Mehrheit der Entwickler wäre es, wie Sie sagten, "überhaupt nicht verwendet". Die Python-Entwickler erkannten jedoch an, dass es einige Nischenfälle gibt, in denen diese Schnittstellenfunktionen wirklich erforderlich sind, und stellten eine benutzerfreundliche API zur Verfügung, um dies zu ermöglichen.
David Culbreth
39

Die Schnittstelle unterstützt Python 2.7 und Python 3.4+.

Um die Schnittstelle zu installieren , müssen Sie

pip install python-interface

Beispielcode:

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
quelle
7
Ein Hauptvorteil dieser Bibliothek, IMHO, ist der frühe Fehler, den sie verursacht: Wenn Ihre Klasse eine angegebene Schnittstelle nicht korrekt implementiert, erhalten Sie eine Ausnahme, sobald die Klasse eingelesen wird - Sie müssen sie nicht einmal verwenden . Mit Pythons eigener abstrakter Basisklasse erhalten Sie die Ausnahme, wenn Sie Ihre Klasse zum ersten Mal instanziieren, was viel später sein kann.
Hans
Es ist unnötig, ABC bietet eine ähnliche, integrierte Funktionalität.
Daniel
@DanielCasares bietet ABC eine tatsächliche Schnittstelle an oder meinen Sie, dass abstrakte Klassen ohne Status oder Implementierungen die Lösung sind, die ABC bietet?
asaf92
35

Das Implementieren von Schnittstellen mit abstrakten Basisklassen ist in modernem Python 3 viel einfacher und dient als Schnittstellenvertrag für Plug-In-Erweiterungen.

Erstellen Sie die Schnittstelle / abstrakte Basisklasse:

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)

Erstellen Sie eine normale Unterklasse und überschreiben Sie alle abstrakten Methoden:

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)

Sie können optional eine gemeinsame Implementierung in den abstrakten Methoden wie in create_sale_invoice()haben und diese super()wie oben explizit in der Unterklasse aufrufen .

Die Instanziierung einer Unterklasse, die nicht alle abstrakten Methoden implementiert, schlägt fehl:

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

Sie können auch abstrakte Eigenschaften, statische und Klassenmethoden verwenden, indem Sie entsprechende Anmerkungen mit kombinieren @abstractmethod.

Abstrakte Basisklassen eignen sich hervorragend für die Implementierung von Plugin-basierten Systemen. Auf alle importierten Unterklassen einer Klasse kann über __subclasses__()zugegriffen werden. Wenn Sie also alle Klassen aus einem Plugin-Verzeichnis mit laden importlib.import_module()und wenn sie die Basisklasse unterklassifizieren, haben Sie direkten Zugriff auf sie über __subclasses__()und Sie können sicher sein, dass der Schnittstellenvertrag für alle erzwungen wird sie während der Instanziierung.

Hier ist die Implementierung zum Laden des Plugins für das AccountingSystemobige Beispiel:

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

Anschließend können Sie über die AccountingSystemKlasse auf das Plugin-Objekt des Buchhaltungssystems zugreifen :

>>> accountingsystem = AccountingSystem.instance()

(Inspiriert von diesem PyMOTW-3-Beitrag .)

mrts
quelle
Frage: Wofür steht der Modulname "ABC"?
Sebastian Nielsen
"ABC" steht für "Abstract Base Classes", siehe offizielle Dokumente
Herr
31

Es gibt Implementierungen von Schnittstellen für Python von Drittanbietern (am beliebtesten sind Zopes , die auch in Twisted verwendet werden ), aber Python-Codierer bevorzugen häufiger das umfangreichere Konzept, das als "Abstract Base Class" (ABC) bekannt ist und eine Schnittstelle mit kombiniert die Möglichkeit, auch dort einige Implementierungsaspekte zu haben. ABCs werden in Python 2.6 und höher besonders gut unterstützt, siehe PEP , aber selbst in früheren Versionen von Python werden sie normalerweise als "der richtige Weg" angesehen. Definieren Sie einfach eine Klasse, deren Methoden teilweise ausgelöst werden, NotImplementedErrorsodass Unterklassen vorhanden sind auf Hinweis, dass sie diese Methoden besser überschreiben sollten! -)

Alex Martelli
quelle
3
Es gibt Implementierungen von Schnittstellen für Python von Drittanbietern. Was bedeutet das? Könnten Sie bitte ABC erklären?
Pratik Deoghare
2
Nun, ich werde Probleme damit haben, dass ABC "reicher" ist. ;) Es gibt Dinge, die zope.interface kann, die ABCs nicht so gut können wie umgekehrt. Aber sonst hast du wie immer recht. +1
Lennart Regebro
1
@Alfred: Das bedeutet, dass Module wie zope.interface nicht in der Standardbibliothek enthalten sind, sondern bei pypi erhältlich sind.
Lennart Regebro
Es fällt mir immer noch schwer, das Konzept von ABC zu verstehen. Wäre es möglich, dass jemand twistedmatrix.com/documents/current/core/howto/components.html (IMHO, eine hervorragende Erklärung des Schnittstellenkonzepts) in Bezug auf ABCs umschreibt ? Macht es Sinn?
Mcepl
21

So etwas (funktioniert möglicherweise nicht, da ich kein Python in der Nähe habe):

class IInterface:
    def show(self): raise NotImplementedError

class MyClass(IInterface):
    def show(self): print "Hello World!"
Bandit
quelle
2
Was soll ich mit __init__(self)dem Konstruktor tun ?
Pratik Deoghare
1
Wie du willst. Da es keine Überprüfung der Kompilierungszeit gegen das Erstellen eines Objekts aus einer abstrakten Klasse gibt, würden Sie beim Codieren / Kompilieren keinen Schutz erhalten. Es wird ein Konstruktor vererbt werden, so dass das Objekt würde erstellt bekommen, nur „leer“ sein. Es liegt an Ihnen, zu entscheiden, ob Sie besser dran sind, indem Sie dies zulassen und Fehler später abfangen, oder das Programm sofort explizit stoppen, indem Sie einen ähnlichen Konstruktor implementieren, der eine Ausnahme auslöst.
Bandi-T
1
Aus diesem Grund abc.ABCist es viel besser als das Erhöhen. Die NotImplementedErrorInstanziierung einer abc.ABCUnterklasse, die nicht alle abstrakten Methoden implementiert, schlägt früh fehl, sodass Sie vor Fehlern geschützt sind. In meiner Antwort unten sehen Sie, wie der Fehler aussieht.
Herr
8

Mein Verständnis ist, dass Schnittstellen in dynamischen Sprachen wie Python nicht so notwendig sind. In Java (oder C ++ mit seiner abstrakten Basisklasse) sind Schnittstellen Mittel, um sicherzustellen, dass Sie beispielsweise den richtigen Parameter übergeben und eine Reihe von Aufgaben ausführen können.

Wenn Sie beispielsweise Observer und Observable haben, ist Observable daran interessiert, Objekte zu abonnieren, die die IObserver-Schnittstelle unterstützen, die wiederum notifyAktionen ausführt . Dies wird beim Kompilieren überprüft.

In Python gibt es keine compile timeund Methodensuchen werden zur Laufzeit durchgeführt. Darüber hinaus kann die Suche mit den magischen Methoden __getattr __ () oder __getattribute __ () überschrieben werden. Mit anderen Worten, Sie können als Beobachter jedes Objekt übergeben, das beim Zugriff auf das notifyAttribut als aufrufbar zurückgegeben werden kann.

Dies führt mich zu dem Schluss, dass Schnittstellen in Python existieren - es ist nur, dass ihre Durchsetzung auf den Moment verschoben wird, in dem sie tatsächlich verwendet werden

Tomasz Zieliński
quelle