Kurzfassung
ABCs bieten ein höheres Maß an semantischem Vertrag zwischen Kunden und den implementierten Klassen.
Lange Version
Es besteht ein Vertrag zwischen einer Klasse und ihren Anrufern. Die Klasse verspricht, bestimmte Dinge zu tun und bestimmte Eigenschaften zu haben.
Der Vertrag hat verschiedene Ebenen.
Auf einer sehr niedrigen Ebene kann der Vertrag den Namen einer Methode oder die Anzahl der Parameter enthalten.
In einer statisch typisierten Sprache würde dieser Vertrag tatsächlich vom Compiler durchgesetzt. In Python können Sie EAFP verwenden oder Introspection eingeben, um zu bestätigen, dass das unbekannte Objekt diesen erwarteten Vertrag erfüllt.
Der Vertrag enthält aber auch übergeordnete semantische Versprechen.
Wenn beispielsweise eine __str__()
Methode vorhanden ist , wird erwartet, dass eine Zeichenfolgendarstellung des Objekts zurückgegeben wird. Es könnte den gesamten Inhalt des Objekts löschen, die Transaktion festschreiben und eine leere Seite aus dem Drucker ausspucken ... aber es gibt ein allgemeines Verständnis dafür, was es tun sollte, wie im Python-Handbuch beschrieben.
Dies ist ein Sonderfall, in dem der semantische Vertrag im Handbuch beschrieben wird. Was soll die print()
Methode tun? Sollte es das Objekt auf einen Drucker oder eine Zeile auf den Bildschirm schreiben oder etwas anderes? Es kommt darauf an - Sie müssen die Kommentare lesen, um den vollständigen Vertrag hier zu verstehen. Ein Teil des Client-Codes, der lediglich überprüft, ob die print()
Methode vorhanden ist, hat einen Teil des Vertrags bestätigt - dass ein Methodenaufruf durchgeführt werden kann, nicht jedoch, dass Übereinstimmung über die Semantik der höheren Ebene des Aufrufs besteht.
Das Definieren einer abstrakten Basisklasse (ABC) ist eine Möglichkeit, einen Vertrag zwischen den Klassenimplementierern und den Aufrufern zu erstellen. Es ist nicht nur eine Liste von Methodennamen, sondern ein gemeinsames Verständnis dessen, was diese Methoden tun sollten. Wenn Sie von diesem ABC erben, versprechen Sie, alle in den Kommentaren beschriebenen Regeln zu befolgen, einschließlich der Semantik der print()
Methode.
Pythons Ententypisierung bietet viele Vorteile in Bezug auf Flexibilität gegenüber statischer Typisierung, löst jedoch nicht alle Probleme. ABCs bieten eine Zwischenlösung zwischen der freien Form von Python und der Bindung und Disziplin einer statisch typisierten Sprache.
__contains__
und einer Klasse, von der erbtcollections.Container
? In Ihrem Beispiel gab es in Python immer ein gemeinsames Verständnis von__str__
. Das Implementieren__str__
macht die gleichen Versprechen wie das Erben von ABC und das anschließende Implementieren__str__
. In beiden Fällen können Sie den Vertrag brechen; Es gibt keine nachweisbare Semantik wie die, die wir bei der statischen Typisierung haben.collections.Container
ist ein entarteter Fall, der nur\_\_contains\_\_
die vordefinierte Konvention einschließt und nur bedeutet. Die Verwendung eines ABC bringt an sich nicht viel Wert, da stimme ich zu. Ich vermute, es wurde hinzugefügt, um (zum Beispiel)Set
zu erlauben, davon zu erben. Zu dem Zeitpunkt, an dem Sie ankommen,Set
hat die Zugehörigkeit zum ABC plötzlich eine beträchtliche Semantik. Ein Gegenstand kann nicht zweimal zur Sammlung gehören. Das ist NICHT durch die Existenz von Methoden erkennbar.Set
ist ein besseres Beispiel alsprint()
. Ich habe versucht, einen Methodennamen zu finden, dessen Bedeutung nicht eindeutig ist und der nicht allein durch den Namen erfasst werden kann. Sie konnten also nicht sicher sein, ob er nur anhand seines Namens und des Python-Handbuchs das Richtige tun würde.Set
als Beispiel anstatt zu schreibenprint
?Set
macht sehr viel Sinn, @Oddthinking.@ Oddthinkings Antwort ist nicht falsch, aber ich denke, sie vermisst den wirklichen , praktischen Grund , warum Python ABCs in einer Welt des Ententypens hat.
Abstrakte Methoden sind ordentlich, aber meiner Meinung nach füllen sie keine Anwendungsfälle aus, die noch nicht durch Entenschreiben abgedeckt sind. Die wahre Kraft abstrakter Basisklassen liegt in der Art und Weise, wie Sie das Verhalten von
isinstance
und anpassen könnenissubclass
. (__subclasshook__
ist im Grunde eine freundlichere API zusätzlich zu Pythons__instancecheck__
und__subclasscheck__
Hooks.) Die Anpassung der integrierten Konstrukte an benutzerdefinierte Typen ist ein wesentlicher Bestandteil der Python-Philosophie.Der Quellcode von Python ist beispielhaft. So wird
collections.Container
in der Standardbibliothek (zum Zeitpunkt des Schreibens) definiert:Diese Definition von
__subclasshook__
besagt, dass jede Klasse mit einem__contains__
Attribut als Unterklasse von Container betrachtet wird, auch wenn sie nicht direkt untergeordnet wird. Also kann ich das schreiben:Mit anderen Worten, wenn Sie die richtige Schnittstelle implementieren, sind Sie eine Unterklasse! ABCs bieten eine formale Möglichkeit, Schnittstellen in Python zu definieren, während sie dem Geist der Ententypisierung treu bleiben. Außerdem funktioniert dies auf eine Weise, die das Open-Closed-Prinzip berücksichtigt .
Das Objektmodell von Python ähnelt oberflächlich dem eines "traditionelleren" OO-Systems (womit ich Java * meine) - wir haben Ihre Klassen, Ihre Objekte, Ihre Methoden - aber wenn Sie die Oberfläche kratzen, werden Sie etwas finden, das viel reicher ist und flexibler. Ebenso mag Pythons Vorstellung von abstrakten Basisklassen für einen Java-Entwickler erkennbar sein, aber in der Praxis sind sie für einen ganz anderen Zweck gedacht.
Manchmal schreibe ich polymorphe Funktionen, die auf ein einzelnes Element oder eine Sammlung von Elementen wirken können, und finde
isinstance(x, collections.Iterable)
, dass sie viel besser lesbar sind alshasattr(x, '__iter__')
ein gleichwertigertry...except
Block. (Wenn Sie Python nicht kennen würden, welcher dieser drei würde die Absicht des Codes am deutlichsten machen?)Trotzdem finde ich, dass ich selten mein eigenes ABC schreiben muss, und ich entdecke die Notwendigkeit eines ABC normalerweise durch Refactoring. Wenn ich sehe, dass eine polymorphe Funktion viele Attributprüfungen durchführt oder viele Funktionen dieselben Attributprüfungen durchführt, deutet dieser Geruch auf die Existenz eines ABC hin, das darauf wartet, extrahiert zu werden.
* ohne in die Debatte darüber zu geraten, ob Java ein "traditionelles" OO-System ist ...
Nachtrag : Obwohl eine abstrakte Basisklasse das Verhalten von
isinstance
und überschreiben kannissubclass
, wird sie immer noch nicht in die MRO der virtuellen Unterklasse eingegeben . Dies ist eine potenzielle Gefahr für Kunden: Nicht für jedes Objektisinstance(x, MyABC) == True
sind die Methoden definiertMyABC
.Leider ist dies eine dieser "Mach das einfach nicht" -Fallen (von denen Python relativ wenige hat!): Vermeiden Sie es, ABCs sowohl mit einer
__subclasshook__
als auch mit nicht abstrakten Methoden zu definieren. Darüber hinaus sollten Sie Ihre Definition__subclasshook__
konsistent mit den von ABC definierten abstrakten Methoden machen.quelle
isinstance(x, collections.Iterable)
ist für mich klarer und ich kenne Python.C
Unterklasse dieabc_method()
geerbte löschen (oder irreparabel vermasseln) lässtMyABC
. Der Hauptunterschied besteht darin, dass es die Oberklasse ist, die den Erbvertrag vermasselt, nicht die Unterklasse.Container.register(ContainAllTheThings)
damit das gegebene Beispiel funktioniert?__subclasshook__
ist "jede Klasse, die dieses Prädikat erfüllt, wird als Unterklasse für die Zweckeisinstance
undissubclass
Überprüfungen betrachtet, unabhängig davon, ob sie beim ABC registriert wurde und ob es sich um eine direkte Unterklasse handelt ". Wie ich in der Antwort sagte, wenn Sie die richtige Schnittstelle implementieren, sind Sie eine Unterklasse!Eine praktische Funktion von ABCs ist, dass Sie, wenn Sie nicht alle erforderlichen Methoden (und Eigenschaften) implementieren, bei der Instanziierung einen Fehler erhalten und nicht
AttributeError
viel später, wenn Sie tatsächlich versuchen, die fehlende Methode zu verwenden.Beispiel aus https://dbader.org/blog/abstract-base-classes-in-python
Bearbeiten: Um die Python3-Syntax einzuschließen, danke @PandasRocks
quelle
Dadurch wird es viel einfacher zu bestimmen, ob ein Objekt ein bestimmtes Protokoll unterstützt, ohne auf das Vorhandensein aller Methoden im Protokoll prüfen zu müssen oder ohne eine Ausnahme tief im "feindlichen" Gebiet auszulösen, da keine Unterstützung erfolgt.
quelle
Abstrakte Methode Stellen Sie sicher, dass jede Methode, die Sie in der übergeordneten Klasse aufrufen, in der untergeordneten Klasse angezeigt werden muss. Im Folgenden finden Sie eine noraml-Methode zum Aufrufen und Verwenden von Abstract. Das in Python3 geschriebene Programm
Normale Art zu telefonieren
Mit abstrakter Methode
Da methodtwo in der untergeordneten Klasse nicht aufgerufen wird, ist ein Fehler aufgetreten. Die richtige Implementierung ist unten
quelle