PEP 3119 – Einführung von Abstrakten Basisklassen
- Autor:
- Guido van Rossum <guido at python.org>, Talin <viridia at gmail.com>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 18. Apr. 2007
- Python-Version:
- 3.0
- Post-History:
- 26. Apr. 2007, 11. Mai 2007
Inhaltsverzeichnis
Zusammenfassung
Dies ist ein Vorschlag zur Hinzufügung von Unterstützung für Abstrakte Basisklassen (ABCs) zu Python 3000. Es wird vorgeschlagen:
- Eine Möglichkeit,
isinstance()undissubclass()zu überladen. - Ein neues Modul
abc, das als „ABC-Unterstützungsframework“ dient. Es definiert eine Metaklasse für die Verwendung mit ABCs und einen Dekorator, der zur Definition abstrakter Methoden verwendet werden kann. - Spezifische ABCs für Container und Iteratoren, die dem `collections`-Modul hinzugefügt werden sollen.
Ein Großteil des Denkens, das in den Vorschlag eingeflossen ist, betrifft nicht den spezifischen Mechanismus von ABCs im Gegensatz zu Schnittstellen oder generischen Funktionen (GFs), sondern die Klärung philosophischer Fragen wie „Was macht eine Menge aus?“, „Was macht ein Mapping aus?“ und „Was macht eine Sequenz aus?“.
Es gibt auch einen begleitenden PEP 3141, der ABCs für numerische Typen definiert.
Danksagungen
Talin schrieb die unten stehende Begründung [1] sowie den größten Teil des Abschnitts über ABCs vs. Schnittstellen. Allein dafür verdient er eine Co-Autorschaft. Der Rest des PEP verwendet „Ich“ in Bezug auf den Erstautor.
Begründung
Im Bereich der objektorientierten Programmierung lassen sich die Muster der Interaktion mit einem Objekt in zwei grundlegende Kategorien einteilen: „Invocation“ und „Inspection“.
Invocation bedeutet die Interaktion mit einem Objekt durch Aufruf seiner Methoden. Normalerweise wird dies mit Polymorphie kombiniert, sodass der Aufruf einer bestimmten Methode je nach Typ eines Objekts unterschiedlichen Code ausführen kann.
Inspection bedeutet die Fähigkeit für externen Code (außerhalb der Methoden des Objekts), den Typ oder die Eigenschaften dieses Objekts zu untersuchen und basierend auf diesen Informationen zu entscheiden, wie dieses Objekt behandelt werden soll.
Beide Nutzungsmuster dienen demselben allgemeinen Zweck, nämlich der Fähigkeit, die Verarbeitung vielfältiger und potenziell neuartiger Objekte auf einheitliche Weise zu unterstützen und gleichzeitig Entscheidungen über die Verarbeitung für jeden verschiedenen Objekttyp anzupassen.
In der klassischen OOP-Theorie ist Invocation das bevorzugte Nutzungsmuster, und Inspection wird aktiv entmutigt und als Relikt eines früheren, prozeduralen Programmierstils betrachtet. In der Praxis ist diese Ansicht jedoch einfach zu dogmatisch und unflexibel und führt zu einer Art Design-Rigidität, die im Widerspruch zur dynamischen Natur einer Sprache wie Python steht.
Insbesondere besteht oft die Notwendigkeit, Objekte auf eine Weise zu verarbeiten, die vom Ersteller der Objektklasse nicht vorhergesehen wurde. Es ist nicht immer die beste Lösung, in jedes Objekt Methoden einzubauen, die den Bedürfnissen jedes möglichen Benutzers dieses Objekts entsprechen. Darüber hinaus gibt es viele mächtige Dispatch-Philosophien, die im direkten Gegensatz zur klassischen OOP-Anforderung stehen, dass Verhalten streng innerhalb eines Objekts gekapselt ist, wie z. B. regel- oder mustergesteuerte Logik.
Einer der Kritikpunkte an Inspection durch klassische OOP-Theoretiker ist andererseits der Mangel an Formalismen und die Ad-hoc-Natur dessen, was inspiziert wird. In einer Sprache wie Python, in der fast jeder Aspekt eines Objekts reflektiert und direkt von externem Code aufgerufen werden kann, gibt es viele verschiedene Möglichkeiten, zu testen, ob ein Objekt ein bestimmtes Protokoll erfüllt oder nicht. Wenn man zum Beispiel fragt: „Ist dieses Objekt ein veränderlicher Sequenzcontainer?“, kann man nach einer Basisklasse „list“ suchen oder nach einer Methode namens „__getitem__“. Beachten Sie jedoch, dass diese Tests zwar offensichtlich erscheinen mögen, aber keiner von ihnen korrekt ist, da einer zu falschen Negativen und der andere zu falschen Positiven führt.
Das allgemein anerkannte Mittel ist, die Tests zu standardisieren und in einer formalen Anordnung zu gruppieren. Dies geschieht am einfachsten, indem jeder Klasse eine Reihe von standardmäßigen testbaren Eigenschaften zugeordnet wird, entweder über den Vererbungsmechanismus oder auf andere Weise. Jeder Test bringt eine Reihe von Versprechen mit sich: Er enthält ein Versprechen über das allgemeine Verhalten der Klasse und ein Versprechen darüber, welche anderen Klassenmethoden verfügbar sein werden.
Dieser PEP schlägt eine bestimmte Strategie zur Organisation dieser Tests vor, die als Abstrakte Basisklassen oder ABCs bekannt ist. ABCs sind einfach Python-Klassen, die in den Vererbungsbaum eines Objekts eingefügt werden, um bestimmte Merkmale dieses Objekts für einen externen Inspektor zu signalisieren. Tests werden mit isinstance() durchgeführt, und die Anwesenheit einer bestimmten ABC bedeutet, dass der Test bestanden wurde.
Zusätzlich definieren die ABCs eine minimale Menge von Methoden, die das charakteristische Verhalten des Typs festlegen. Code, der Objekte anhand ihres ABC-Typs unterscheidet, kann darauf vertrauen, dass diese Methoden immer vorhanden sind. Jede dieser Methoden wird von einer verallgemeinerten abstrakten semantischen Definition begleitet, die in der Dokumentation der ABC beschrieben ist. Diese Standard-Semantik-Definitionen werden nicht erzwungen, aber dringend empfohlen.
Wie alle anderen Dinge in Python sind diese Versprechen eher eine freundliche Vereinbarung, was bedeutet, dass die Sprache zwar einige der in der ABC gemachten Versprechen erzwingt, es aber dem Implementierer der konkreten Klasse obliegt, sicherzustellen, dass die restlichen eingehalten werden.
Spezifikation
Die Spezifikation folgt den im Abstract aufgeführten Kategorien
- Eine Möglichkeit,
isinstance()undissubclass()zu überladen. - Ein neues Modul
abc, das als „ABC-Unterstützungsframework“ dient. Es definiert eine Metaklasse für die Verwendung mit ABCs und einen Dekorator, der zur Definition abstrakter Methoden verwendet werden kann. - Spezifische ABCs für Container und Iteratoren, die dem `collections`-Modul hinzugefügt werden sollen.
Überladen von isinstance() und issubclass()
Während der Entwicklung dieses PEP und seines Begleitdokuments PEP 3141 standen wir wiederholt vor der Wahl, entweder mehr, feingranulare ABCs oder weniger, grob granulare ABCs zu standardisieren. Zum Beispiel führte PEP 3141 zu einem Zeitpunkt die folgende Hierarchie von Basisklassen für komplexe Zahlen ein: MonoidUnderPlus, AdditiveGroup, Ring, Field, Complex (jeweils abgeleitet von der vorherigen). Und die Diskussion erwähnte mehrere andere algebraische Kategorisierungen, die weggelassen wurden: Algebraic, Transcendental und IntegralDomain, und PrincipalIdealDomain. In früheren Versionen des aktuellen PEP haben wir die Anwendungsfälle für separate Klassen wie Set, ComposableSet, MutableSet, HashableSet, MutableComposableSet, HashableComposableSet berücksichtigt.
Das Dilemma hier ist, dass wir lieber weniger ABCs hätten, aber was soll ein Benutzer tun, der eine weniger verfeinerte ABC benötigt? Betrachten Sie z. B. das Los eines Mathematikers, der seine eigene Art von Transzendentalzahlen definieren möchte, aber auch möchte, dass float und int als Transzendental betrachtet werden. PEP 3141 schlug ursprünglich vor, `float.__bases__` für diesen Zweck zu patchen, aber es gibt einige gute Gründe, die eingebauten Typen unveränderlich zu halten (zum einen werden sie von allen Python-Interpretern im selben Adressraum gemeinsam genutzt, wie sie von mod_python verwendet werden [16]).
Ein weiteres Beispiel wäre jemand, der eine generische Funktion (PEP 3124) für jede Sequenz definieren möchte, die eine `append()`-Methode hat. Die `Sequence`-ABC (siehe unten) garantiert die `append()`-Methode nicht, während `MutableSequence` nicht nur `append()`, sondern auch verschiedene andere mutierende Methoden erfordert.
Um diese und ähnliche Dilemmata zu lösen, wird im nächsten Abschnitt eine Metaklasse für die Verwendung mit ABCs vorgeschlagen, die es uns ermöglicht, eine ABC als „virtuelle Basisklasse“ (nicht dasselbe Konzept wie in C++) zu jeder Klasse hinzuzufügen, auch zu einer anderen ABC. Dies ermöglicht es der Standardbibliothek, die ABCs `Sequence` und `MutableSequence` zu definieren und diese als virtuelle Basisklassen für integrierte Typen wie `basestring`, `tuple` und `list` zu registrieren, sodass zum Beispiel die folgenden Bedingungen alle wahr sind
isinstance([], Sequence)
issubclass(list, Sequence)
issubclass(list, MutableSequence)
isinstance((), Sequence)
not issubclass(tuple, MutableSequence)
isinstance("", Sequence)
issubclass(bytearray, MutableSequence)
Der primär vorgeschlagene Mechanismus ist, die integrierten Funktionen isinstance() und issubclass() überladen zu können. Das Überladen funktioniert wie folgt: Der Aufruf isinstance(x, C) prüft zuerst, ob C.__instancecheck__ existiert, und wenn ja, ruft C.__instancecheck__(x) anstelle seiner normalen Implementierung auf. Ebenso prüft der Aufruf issubclass(D, C) zuerst, ob C.__subclasscheck__ existiert, und wenn ja, ruft C.__subclasscheck__(D) anstelle seiner normalen Implementierung auf.
Beachten Sie, dass die magischen Namen nicht __isinstance__ und __issubclass__ sind; dies liegt daran, dass die Umkehrung der Argumente zu Verwirrung führen könnte, insbesondere für den issubclass()-Überlader.
Eine prototypische Implementierung davon ist in [12] gegeben.
Hier ist ein Beispiel mit (naiverweise einfachen) Implementierungen von __instancecheck__ und __subclasscheck__
class ABCMeta(type):
def __instancecheck__(cls, inst):
"""Implement isinstance(inst, cls)."""
return any(cls.__subclasscheck__(c)
for c in {type(inst), inst.__class__})
def __subclasscheck__(cls, sub):
"""Implement issubclass(sub, cls)."""
candidates = cls.__dict__.get("__subclass__", set()) | {cls}
return any(c in candidates for c in sub.mro())
class Sequence(metaclass=ABCMeta):
__subclass__ = {list, tuple}
assert issubclass(list, Sequence)
assert issubclass(tuple, Sequence)
class AppendableSequence(Sequence):
__subclass__ = {list}
assert issubclass(list, AppendableSequence)
assert isinstance([], AppendableSequence)
assert not issubclass(tuple, AppendableSequence)
assert not isinstance((), AppendableSequence)
Der nächste Abschnitt schlägt eine vollwertige Implementierung vor.
Das Modul abc: Ein ABC-Unterstützungsframework
Das neue Standardbibliotheksmodul abc, geschrieben in reinem Python, dient als Framework zur Unterstützung von ABCs. Es definiert eine Metaklasse ABCMeta und die Dekoratoren @abstractmethod und @abstractproperty. Eine Beispielimplementierung wird von [13] bereitgestellt.
Die Klasse ABCMeta überschreibt __instancecheck__ und __subclasscheck__ und definiert eine Methode register. Die Methode register nimmt ein Argument entgegen, das eine Klasse sein muss; nach dem Aufruf B.register(C) gibt der Aufruf issubclass(C, B) True zurück, da B.__subclasscheck__(C) True zurückgibt. Ebenso ist isinstance(x, B) äquivalent zu issubclass(x.__class__, B) or issubclass(type(x), B). (Es ist möglich, dass type(x) und x.__class__ nicht dasselbe Objekt sind, z. B. wenn x ein Proxy-Objekt ist.)
Diese Methoden sind für Aufrufe auf Klassen bestimmt, deren Metaklasse (von) ABCMeta ist; zum Beispiel
from abc import ABCMeta
class MyABC(metaclass=ABCMeta):
pass
MyABC.register(tuple)
assert issubclass(tuple, MyABC)
assert isinstance((), MyABC)
Die letzten beiden Asserts sind äquivalent zu den folgenden beiden
assert MyABC.__subclasscheck__(tuple)
assert MyABC.__instancecheck__(())
Natürlich können Sie auch direkt von `MyABC` ableiten
class MyClass(MyABC):
pass
assert issubclass(MyClass, MyABC)
assert isinstance(MyClass(), MyABC)
Auch ist ein Tupel natürlich keine MyClass
assert not issubclass(tuple, MyClass)
assert not isinstance((), MyClass)
Sie können eine andere Klasse als Unterklasse von MyClass registrieren
MyClass.register(list)
assert issubclass(list, MyClass)
assert issubclass(list, MyABC)
Sie können auch eine andere ABC registrieren
class AnotherClass(metaclass=ABCMeta):
pass
AnotherClass.register(basestring)
MyClass.register(AnotherClass)
assert isinstance(str, MyABC)
Der letzte Assert erfordert das Nachverfolgen der folgenden Ober-/Unterklassen-Beziehungen
MyABC -> MyClass (using regular subclassing)
MyClass -> AnotherClass (using registration)
AnotherClass -> basestring (using registration)
basestring -> str (using regular subclassing)
Das Modul abc definiert außerdem einen neuen Dekorator, @abstractmethod, der zur Deklaration abstrakter Methoden verwendet wird. Eine Klasse, die mindestens eine Methode enthält, die mit diesem Dekorator deklariert wurde und noch nicht überschrieben wurde, kann nicht instanziiert werden. Solche Methoden können von der überschreibenden Methode in der Unterklasse aufgerufen werden (mittels super oder direktem Aufruf). Zum Beispiel
from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
@abstractmethod
def foo(self): pass
A() # raises TypeError
class B(A):
pass
B() # raises TypeError
class C(A):
def foo(self): print(42)
C() # works
Hinweis: Der Dekorator @abstractmethod sollte nur innerhalb eines Klassenkörpers verwendet werden und nur für Klassen, deren Metaklasse (von) ABCMeta ist. Das dynamische Hinzufügen abstrakter Methoden zu einer Klasse oder der Versuch, den Abstraktionsstatus einer Methode oder Klasse zu ändern, sobald sie erstellt wurde, wird nicht unterstützt. @abstractmethod beeinflusst nur Unterklassen, die durch reguläre Vererbung abgeleitet wurden; „virtuelle Unterklassen“, die mit der Methode register() registriert wurden, sind nicht betroffen.
Implementierung: Der Dekorator @abstractmethod setzt das Funktionsattribut __isabstractmethod__ auf den Wert True. Die Methode ABCMeta.__new__ berechnet das Typattribut __abstractmethods__ als die Menge aller Methodennamen, die ein __isabstractmethod__-Attribut mit dem Wert `True` haben. Dies geschieht durch Kombination der __abstractmethods__-Attribute der Basisklassen, Hinzufügen der Namen aller Methoden im neuen Klassenwörterbuch, die ein wahres __isabstractmethod__-Attribut haben, und Entfernen der Namen aller Methoden im neuen Klassenwörterbuch, die kein wahres __isabstractmethod__-Attribut haben. Wenn die resultierende __abstractmethods__-Menge nicht leer ist, gilt die Klasse als abstrakt, und Versuche, sie zu instanziieren, lösen TypeError aus. (Wenn dies in CPython implementiert wäre, könnte ein internes Flag Py_TPFLAGS_ABSTRACT verwendet werden, um diese Prüfung zu beschleunigen [6].)
Diskussion: Im Gegensatz zu abstrakten Methoden in Java oder reinen abstrakten Methoden in C++ können abstrakte Methoden wie hier definiert auch eine Implementierung haben. Diese Implementierung kann über den super-Mechanismus aus der Klasse aufgerufen werden, die sie überschreibt. Dies könnte als Endpunkt für einen Super-Aufruf in einem Framework nützlich sein, das kooperative Mehrfachvererbung verwendet [7], [8].
Ein zweiter Dekorator, @abstractproperty, wird zur Definition abstrakter Datenattribute definiert. Seine Implementierung ist eine Unterklasse der integrierten Klasse property, die ein __isabstractmethod__-Attribut hinzufügt
class abstractproperty(property):
__isabstractmethod__ = True
Es kann auf zwei Arten verwendet werden
class C(metaclass=ABCMeta):
# A read-only property:
@abstractproperty
def readonly(self):
return self.__x
# A read-write property (cannot use decorator syntax):
def getx(self):
return self.__x
def setx(self, value):
self.__x = value
x = abstractproperty(getx, setx)
Ähnlich wie bei abstrakten Methoden kann eine Unterklasse, die eine abstrakte Eigenschaft erbt (deklariert entweder mit der Dekoratorsyntax oder der längeren Form), nicht instanziiert werden, es sei denn, sie überschreibt diese abstrakte Eigenschaft mit einer konkreten Eigenschaft.
ABCs für Container und Iteratoren
Das Modul collections wird die für die Arbeit mit Mengen, Mappings, Sequenzen und einigen Hilfstypen wie Iteratoren und Dictionary-Ansichten notwendigen und ausreichenden ABCs definieren. Alle ABCs haben die oben erwähnte ABCMeta als ihre Metaklasse.
Die ABCs bieten Implementierungen ihrer abstrakten Methoden, die technisch gültig, aber ziemlich nutzlos sind; z. B. gibt __hash__ 0 zurück, und __iter__ gibt einen leeren Iterator zurück. Im Allgemeinen repräsentieren die abstrakten Methoden das Verhalten eines leeren Containers des angegebenen Typs.
Einige ABCs bieten auch konkrete (d. h. nicht-abstrakte) Methoden an; beispielsweise hat die Klasse Iterator eine Methode __iter__, die sich selbst zurückgibt und damit eine wichtige Invariante von Iteratoren erfüllt (die in Python 2 von jeder Iterator-Klasse neu implementiert werden musste). Diese ABCs können als „Mix-in“-Klassen betrachtet werden.
Keine in diesem PEP definierten ABCs überschreiben __init__, __new__, __str__ oder __repr__. Die Definition einer Standardkonstruktorsignatur würde benutzerdefinierte Containertypen, z. B. Patricia-Bäume oder gdbm-Dateien, unnötig einschränken. Die Definition einer spezifischen Zeichenkettendarstellung für eine Sammlung bleibt ebenfalls den einzelnen Implementierungen überlassen.
Hinweis: Es gibt keine ABCs für Ordnungsvorgänge (__lt__, __le__, __ge__, __gt__). Die Definition dieser in einer Basisklasse (abstrakt oder nicht) führt zu Problemen mit dem akzeptierten Typ für den zweiten Operanden. Zum Beispiel würde, wenn die Klasse Ordering __lt__ definierte, angenommen, dass für alle Ordering-Instanzen x und y gilt, dass x < y definiert ist (selbst wenn es nur eine partielle Ordnung definiert). Aber das kann nicht der Fall sein: Wenn sowohl list als auch str von Ordering abgeleitet würden, würde dies bedeuten, dass [1, 2] < (1, 2) definiert sein sollte (und vermutlich False zurückgeben würde), während in der Tat (in Python 3000!) solche „Mixed-Mode-Vergleiche“ explizit verboten sind und TypeError auslösen. Siehe PEP 3100 und [14] für weitere Informationen. (Dies ist ein Sonderfall eines allgemeineren Problems mit Operationen, die ein anderes Argument desselben Typs entgegennehmen.)
One-Trick-Ponys
Diese abstrakten Klassen repräsentieren einzelne Methoden wie __iter__ oder __len__.
Hashable- Die Basisklasse für Klassen, die
__hash__definieren. Die Methode__hash__sollte eine Ganzzahl zurückgeben. Die abstrakte Methode__hash__gibt immer 0 zurück, was eine gültige (wenn auch ineffiziente) Implementierung ist. **Invariante:** Wenn die KlassenC1undC2beide vonHashableabgeleitet sind, muss die Bedingungo1 == o2implizierenhash(o1) == hash(o2)für alle Instanzeno1vonC1und alle Instanzeno2vonC2. Mit anderen Worten, zwei Objekte sollten niemals gleich sein, wenn sie unterschiedliche Hash-Werte haben.Eine weitere Einschränkung ist, dass hashbare Objekte nach ihrer Erstellung niemals ihren Wert (verglichen mit `==`) oder ihren Hash-Wert ändern sollten. Wenn eine Klasse dies nicht garantieren kann, sollte sie nicht von
Hashableabgeleitet werden; wenn sie dies für bestimmte Instanzen nicht garantieren kann, sollte__hash__für diese Instanzen eineTypeError-Ausnahme auslösen.Hinweis: Das Sein einer Instanz dieser Klasse impliziert nicht, dass ein Objekt unveränderlich ist; z. B. ist ein Tupel, das eine Liste als Mitglied enthält, nicht unveränderlich; seine
__hash__-Methode löstTypeErroraus. (Dies liegt daran, dass es rekursiv versucht, den Hash jedes Mitglieds zu berechnen; wenn ein Mitglied nicht hashbar ist, löst esTypeErroraus.) Iterable- Die Basisklasse für Klassen, die
__iter__definieren. Die Methode__iter__sollte immer eine Instanz vonIterator(siehe unten) zurückgeben. Die abstrakte Methode__iter__gibt einen leeren Iterator zurück. Iterator- Die Basisklasse für Klassen, die
__next__definieren. Diese leitet vonIterableab. Die abstrakte Methode__next__löstStopIterationaus. Die konkrete Methode__iter__gibtselfzurück. Beachten Sie die Unterscheidung zwischenIterableundIterator: EinIterablekann iteriert werden, d. h. unterstützt die__iter__-Methoden; einIteratorist das, was die eingebaute Funktioniter()zurückgibt, d. h. unterstützt die__next__-Methode. Sized- Die Basisklasse für Klassen, die
__len__definieren. Die Methode__len__sollte eineInteger(siehe „Zahlen“ unten) >= 0 zurückgeben. Die abstrakte Methode__len__gibt 0 zurück. **Invariante:** Wenn eine KlasseCvonSizedsowie vonIterableabgeleitet ist, sollte die Invariantesum(1 for x in c) == len(c)für jede InstanzcvonCgelten. Container- Die Basisklasse für Klassen, die
__contains__definieren. Die Methode__contains__sollte einboolzurückgeben. Die abstrakte Methode__contains__gibtFalsezurück. **Invariante:** Wenn eine KlasseCvonContainersowie vonIterableabgeleitet ist, dann sollte(x in c for x in c)ein Generator sein, der nur True-Werte für jede InstanzcvonCliefert.
Offene Fragen: Konzeptionell könnten diese Klassen anstelle der Metaklasse ABCMeta __instancecheck__ und __subclasscheck__ überschreiben, um die Anwesenheit der anwendbaren Spezialmethode zu prüfen; zum Beispiel
class Sized(metaclass=ABCMeta):
@abstractmethod
def __hash__(self):
return 0
@classmethod
def __instancecheck__(cls, x):
return hasattr(x, "__len__")
@classmethod
def __subclasscheck__(cls, C):
return hasattr(C, "__bases__") and hasattr(C, "__len__")
Dies hat den Vorteil, keine explizite Registrierung zu erfordern. Die Semantik ist jedoch schwer exakt richtig zu bekommen, angesichts der verwirrenden Semantik von Instanzattributen gegenüber Klassenattributen und dass eine Klasse eine Instanz ihrer Metaklasse ist; die Prüfung auf __bases__ ist nur eine Annäherung an die gewünschte Semantik. **Strohhalm-Mann:** Machen wir es, aber arrangieren wir es so, dass auch die Registrierungs-API funktioniert.
Mengen
Diese abstrakten Klassen repräsentieren schreibgeschützte Mengen und veränderbare Mengen. Die grundlegendste Mengenoperation ist der Mitgliedschaftstest, geschrieben als x in s und implementiert durch s.__contains__(x). Diese Operation ist bereits durch die oben definierte Klasse Container definiert. Daher definieren wir eine Menge als eine größenbezogene, iterierbare Sammlung, für die bestimmte Invarianten der mathematischen Mengentheorie gelten.
Der eingebaute Typ `set` leitet sich von MutableSet ab. Der eingebaute Typ `frozenset` leitet sich von Set und Hashable ab.
Set- Dies ist ein größenbezogener, iterierbarer Container, d. h. eine Unterklasse von
Sized,IterableundContainer. Nicht jede Unterklasse dieser drei Klassen ist jedoch eine Menge! Mengen haben die zusätzliche Invariante, dass jedes Element nur einmal vorkommt (wie durch Iteration bestimmt werden kann), und zusätzlich definieren Mengen konkrete Operatoren, die die Ungleichheitsoperationen als Teilmengen-/Obermengentests implementieren. Im Allgemeinen gelten die Invarianten für endliche Mengen in der Mathematik. [11]Mengen mit unterschiedlichen Implementierungen können sicher, (normalerweise) effizient und korrekt unter Verwendung der mathematischen Definitionen der Teilmengen-/Obermengenoperationen für endliche Mengen verglichen werden. Die Ordnungsvorgänge haben konkrete Implementierungen; Unterklassen können diese aus Geschwindigkeitsgründen überschreiben, sollten aber die Semantik beibehalten. Da
SetvonSizedabgeleitet ist, kann__eq__eine Abkürzung nehmen und sofortFalsezurückgeben, wenn zwei Mengen ungleicher Länge verglichen werden. Ebenso kann__le__sofortFalsezurückgeben, wenn die erste Menge mehr Elemente als die zweite Menge hat. Beachten Sie, dass die Mengeninklusion nur eine partielle Ordnung implementiert; z. B. sind{1, 2}und{1, 3}nicht geordnet (alle drei von<,==und>geben für diese ArgumenteFalsezurück). Mengen können nicht relativ zu Mappings oder Sequenzen geordnet werden, aber sie können mit diesen auf Gleichheit verglichen werden (und dann vergleichen sie sich immer ungleich).Diese Klasse definiert auch konkrete Operatoren zur Berechnung der Vereinigung, Schnittmenge, symmetrischen und asymmetrischen Differenz, nämlich
__or__,__and__,__xor__und__sub__. Diese Operatoren sollten Instanzen vonSetzurückgeben. Die Standardimplementierungen rufen die überschreibbare Klassenmethode_from_iterable()mit einem iterierbaren Argument auf. Die Standardimplementierung dieser Factory-Methode gibt einefrozenset-Instanz zurück; sie kann überschrieben werden, um eine andere geeigneteSet-Unterklasse zurückzugeben.Schließlich definiert diese Klasse eine konkrete Methode
_hash, die den Hashwert aus den Elementen berechnet. Hashbare Unterklassen vonSetkönnen__hash__durch Aufruf von_hashimplementieren, oder sie können denselben Algorithmus effizienter neu implementieren; der implementierte Algorithmus sollte jedoch derselbe sein. Derzeit ist der Algorithmus nur durch den Quellcode vollständig spezifiziert [15].Hinweis: Die Methoden
issubsetundissuperset, die im Set-Typ in Python 2 zu finden sind, werden nicht unterstützt, da diese größtenteils nur Aliase für__le__und__ge__sind. MutableSet- Dies ist eine Unterklasse von
Set, die zusätzliche Operationen zum Hinzufügen und Entfernen von Elementen implementiert. Die unterstützten Methoden haben die Semantik, die vomset-Typ in Python 2 bekannt ist (außerdiscard, das nach Java modelliert ist).add(x)- Abstrakte Methode, die eine
boolzurückgibt, welche das Elementxhinzufügt, falls es noch nicht im Set vorhanden ist. Sie sollteTruezurückgeben, wennxhinzugefügt wurde, undFalse, wenn es bereits vorhanden war. Die abstrakte Implementierung wirftNotImplementedError. .discard(x)- Abstrakte Methode, die eine
boolzurückgibt, welche das Elementxentfernt, falls es vorhanden ist. Sie sollteTruezurückgeben, wenn das Element vorhanden war, undFalse, wenn es nicht vorhanden war. Die abstrakte Implementierung wirftNotImplementedError. .pop()- Konkrete Methode, die ein beliebiges Element entfernt und zurückgibt. Wenn das Set leer ist, wirft es
KeyError. Die Standardimplementierung entfernt das erste Element, das vom Iterator des Sets zurückgegeben wird. .toggle(x)- Konkrete Methode, die eine
boolzurückgibt, welche x zum Set hinzufügt, falls es nicht vorhanden war, es aber entfernt, falls es vorhanden war. Sie sollteTruezurückgeben, wennxhinzugefügt wurde, undFalse, wenn es entfernt wurde. .clear()- Konkrete Methode, die das Set leert. Die Standardimplementierung ruft wiederholt
self.pop()auf, bisKeyErrorgefangen wird. (Hinweis: Dies ist wahrscheinlich viel langsamer als das einfache Erstellen eines neuen Sets, auch wenn eine Implementierung es mit einem schnelleren Ansatz überschreibt; in einigen Fällen ist jedoch die Objektidentität wichtig.)
Dies unterstützt auch die In-Place-mutierenden Operationen
|=,&=,^=,-=. Dies sind konkrete Methoden, deren rechtes Operand ein beliebigesIterablesein kann, mit Ausnahme von&=, dessen rechtes Operand einContainersein muss. Diese ABC stellt nicht die benannten Methoden des integrierten konkretenset-Typs bereit, die (fast) die gleichen Operationen durchführen.
Mappings
Diese abstrakten Klassen repräsentieren schreibgeschützte und mutable Mappings. Die Klasse Mapping repräsentiert die gängigste API für schreibgeschützte Mappings.
Der eingebaute Typ dict leitet sich von MutableMapping ab.
Mapping- Eine Unterklasse von
Container,IterableundSized. Die Schlüssel eines Mappings bilden naturgemäß ein Set. Die (Schlüssel, Wert)-Paare (die Tupel sein müssen) werden auch als Elemente bezeichnet. Die Elemente bilden ebenfalls ein Set. Methoden.__getitem__(key)- Abstrakte Methode, die den Wert zurückgibt, der
keyentspricht, oderKeyErrorwirft. Die Implementierung wirft immerKeyError. .get(key, default=None)- Konkrete Methode, die
self[key]zurückgibt, wenn dies keineKeyErrorauslöst, und dendefault-Wert, wenn doch. .__contains__(key)- Konkrete Methode, die
Truezurückgibt, wennself[key]keineKeyErrorauslöst, undFalse, wenn doch. .__len__()- Abstrakte Methode, die die Anzahl der eindeutigen Schlüssel zurückgibt (d. h. die Länge des Schlüssel-Sets).
.__iter__()- Abstrakte Methode, die jeden Schlüssel im Schlüssel-Set genau einmal zurückgibt.
.keys()- Konkrete Methode, die das Schlüssel-Set als
Setzurückgibt. Die Standard-Konkretimplementierung gibt eine „Ansicht“ des Schlüssel-Sets zurück (was bedeutet, dass sich der Wert der Ansicht entsprechend ändert, wenn das zugrunde liegende Mapping geändert wird); Unterklassen müssen keine Ansicht zurückgeben, aber sie sollten einSetzurückgeben. .items()- Konkrete Methode, die die Elemente als
Setzurückgibt. Die Standard-Konkretimplementierung gibt eine „Ansicht“ des Element-Sets zurück; Unterklassen müssen keine Ansicht zurückgeben, aber sie sollten einSetzurückgeben. .values()- Konkrete Methode, die die Werte als größenveränderliches, iterierbares Container (kein Set!) zurückgibt. Die Standard-Konkretimplementierung gibt eine „Ansicht“ der Werte des Mappings zurück; Unterklassen müssen keine Ansicht zurückgeben, aber sie sollten einen größenveränderlichen, iterierbaren Container zurückgeben.
Die folgenden Invarianten sollten für jedes Mapping
mgeltenlen(m.values()) == len(m.keys()) == len(m.items()) == len(m) [value for value in m.values()] == [m[key] for key in m.keys()] [item for item in m.items()] == [(key, m[key]) for key in m.keys()]
d.h. das Iterieren über die Elemente, Schlüssel und Werte sollte die Ergebnisse in derselben Reihenfolge zurückgeben.
MutableMapping- Eine Unterklasse von
Mapping, die auch einige Standard-mutierende Methoden implementiert. Abstrakte Methoden sind__setitem__,__delitem__. Konkrete Methoden sindpop,popitem,clear,update. Hinweis:setdefaultist *nicht* enthalten. Offene Punkte: Spezifikationen für die Methoden ausarbeiten.
Sequenzen
Diese abstrakten Klassen repräsentieren schreibgeschützte und mutable Sequenzen.
Die integrierten Typen list und bytes leiten sich von MutableSequence ab. Die integrierten Typen tuple und str leiten sich von Sequence und Hashable ab.
Sequence- Eine Unterklasse von
Iterable,Sized,Container. Sie definiert eine neue abstrakte Methode__getitem__, die eine etwas komplizierte Signatur hat: wenn sie mit einer Ganzzahl aufgerufen wird, gibt sie ein Element der Sequenz zurück oder wirftIndexError; wenn sie mit einemslice-Objekt aufgerufen wird, gibt sie eine andereSequencezurück. Die konkrete__iter__-Methode iteriert über die Elemente unter Verwendung von__getitem__mit den Ganzzahlargumenten 0, 1 und so weiter, bisIndexErrorgeworfen wird. Die Länge sollte gleich der Anzahl der vom Iterator zurückgegebenen Werte sein.Offene Punkte: Andere Kandidatenmethoden, die alle Standard-konkrete Implementierungen haben können, die nur von
__len__und__getitem__mit einem Ganzzahlargument abhängen:__reversed__,index,count,__add__,__mul__. MutableSequence- Eine Unterklasse von
Sequence, die einige Standard-mutierende Methoden hinzufügt. Abstrakte mutierende Methoden:__setitem__(für Ganzzahlindizes sowie Slices),__delitem__(ebenso),insert. Konkrete mutierende Methoden:append,reverse,extend,pop,remove. Konkrete mutierende Operatoren:+=,*=(diese mutieren das Objekt inplace). Hinweis: Dies definiert nichtsort()– diese muss nur auf echtenlist-Instanzen vorhanden sein.
Strings
Python 3000 wird wahrscheinlich mindestens zwei integrierte String-Typen haben: Byte-Strings (bytes), die von MutableSequence abgeleitet sind, und (Unicode-)Zeichenketten (str), die von Sequence und Hashable abgeleitet sind.
Offene Punkte: Die Basisinterfaces für diese definieren, damit alternative Implementierungen und Unterklassen wissen, worauf sie sich einlassen. Dies könnte Gegenstand eines neuen PEP oder mehrerer PEPs sein (PEP 358 sollte für den bytes-Typ übernommen werden).
ABCs vs. Alternativen
In diesem Abschnitt werde ich versuchen, ABCs mit anderen vorgeschlagenen Ansätzen zu vergleichen und gegenüberzustellen.
ABCs vs. Duck Typing
Bedeutet die Einführung von ABCs das Ende von Duck Typing? Ich glaube nicht. Python wird nicht verlangen, dass eine Klasse von BasicMapping oder Sequence abgeleitet wird, wenn sie eine __getitem__-Methode definiert, noch wird die x[y]-Syntax verlangen, dass x eine Instanz einer dieser ABCs ist. Sie können immer noch jedes „file-like“-Objekt an sys.stdout zuweisen, solange es eine write-Methode hat.
Natürlich wird es einige Anreize geben, Benutzer zu ermutigen, von den entsprechenden Basisklassen abzuleiten; diese reichen von Standardimplementierungen für bestimmte Funktionalitäten bis hin zu einer verbesserten Möglichkeit, zwischen Mappings und Sequenzen zu unterscheiden. Aber es gibt keine Zwänge. Wenn hasattr(x, "__len__") für Sie funktioniert, großartig! ABCs sind dazu gedacht, Probleme zu lösen, die in Python 2 gar keine gute Lösung haben, wie z. B. die Unterscheidung zwischen Mappings und Sequenzen.
ABCs vs. Generische Funktionen
ABCs sind mit Generic Functions (GFs) kompatibel. Zum Beispiel verwendet meine eigene Generic Functions-Implementierung [4] die Klassen (Typen) der Argumente als Dispatch-Schlüssel, was es abgeleiteten Klassen ermöglicht, Basisklassen zu überschreiben. Da (aus Pythons Sicht) ABCs ganz normale Klassen sind, kann die Verwendung einer ABC in der Standardimplementierung für eine GF sehr angemessen sein. Wenn ich zum Beispiel eine überladene prettyprint-Funktion habe, wäre es sehr sinnvoll, das Pretty-Printing von Sets wie folgt zu definieren
@prettyprint.register(Set)
def pp_set(s):
return "{" + ... + "}" # Details left as an exercise
und Implementierungen für spezifische Unterklassen von Set könnten leicht hinzugefügt werden.
Ich glaube, dass ABCs auch keine Probleme für RuleDispatch, Phillips Ebys GF-Implementierung in PEAK [5] darstellen werden.
Natürlich könnten GF-Befürworter behaupten, dass GFs (und konkrete oder Implementierungsklassen) alles sind, was Sie brauchen. Aber selbst sie werden die Nützlichkeit der Vererbung nicht leugnen; und man kann die in diesem PEP vorgeschlagenen ABCs leicht als optionale Implementierungsbasisklassen betrachten; es gibt keine Anforderung, dass alle benutzerdefinierten Mappings von BasicMapping abgeleitet werden müssen.
ABCs vs. Schnittstellen
ABCs sind nicht intrinsisch mit Interfaces inkompatibel, aber es gibt erhebliche Überschneidungen. Vorerst überlasse ich es den Befürwortern von Interfaces, zu erklären, warum Interfaces besser sind. Ich erwarte, dass viel von der Arbeit, die z. B. in die Definition der verschiedenen Abstufungen von „Mapping-Art“ und der Nomenklatur geflossen ist, leicht für einen Vorschlag zur Verwendung von Interfaces anstelle von ABCs adaptiert werden könnte.
„Interfaces“ bezieht sich in diesem Zusammenhang auf eine Reihe von Vorschlägen für zusätzliche Metadaten-Elemente, die einer Klasse zugeordnet sind und nicht Teil der regulären Klassenhierarchie sind, aber bestimmte Arten von Vererbungstests ermöglichen.
Solche Metadaten würden, zumindest in einigen Vorschlägen, so gestaltet sein, dass sie von einer Anwendung leicht geändert werden können, was es Anwendungsentwicklern ermöglicht, die normale Klassifizierung eines Objekts zu überschreiben.
Der Nachteil dieser Idee, mutable Metadaten an eine Klasse anzuhängen, ist, dass Klassen geteilter Zustand sind und ihre Änderung zu Konflikten der Absicht führen kann. Darüber hinaus kann die Notwendigkeit, die Klassifizierung eines Objekts zu überschreiben, mit generischen Funktionen sauberer erledigt werden: Im einfachsten Fall kann eine generische Funktion „Kategorienmitgliedschaft“ definiert werden, die in der Basisimplementierung einfach False zurückgibt, und dann können Überschreibungen bereitgestellt werden, die für alle interessierenden Klassen True zurückgeben.
Referenzen
[2] Unvollständiges Implementierungsprototyp, von GvR (https://web.archive.org/web/20170223133820/http://svn.python.org/view/sandbox/trunk/abc/)
[3] Mögliche Python 3K Klassenhierarchie?, Wiki-Seite erstellt von Bill Janssen (https://wiki.python.org/moin/AbstractBaseClasses)
[9] Partielle Ordnung, in Wikipedia (https://en.wikipedia.org/wiki/Partial_order)
[10] Totale Ordnung, in Wikipedia (https://en.wikipedia.org/wiki/Total_order)
frozenset_hash() in Object/setobject.c (https://web.archive.org/web/20170224204758/http://svn.python.org/view/python/trunk/Objects/setobject.c)Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-3119.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT