@ 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:
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if any("__contains__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
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:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
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 als hasattr(x, '__iter__')
ein gleichwertiger try...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 kann issubclass
, wird sie immer noch nicht in die MRO der virtuellen Unterklasse eingegeben . Dies ist eine potenzielle Gefahr für Kunden: Nicht für jedes Objekt isinstance(x, MyABC) == True
sind die Methoden definiert MyABC
.
class MyABC(metaclass=abc.ABCMeta):
def abc_method(self):
pass
@classmethod
def __subclasshook__(cls, C):
return True
class C(object):
pass
# typical client code
c = C()
if isinstance(c, MyABC): # will be true
c.abc_method() # raises AttributeError
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.
__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.