PEP 245 – Python Interface Syntax
- Autor:
- Michel Pelletier <michel at users.sourceforge.net>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 11. Jan 2001
- Python-Version:
- 2.2
- Post-History:
- 21. Mär 2001
Inhaltsverzeichnis
- Ablehnungsbescheid
- Einleitung
- Übersicht
- Das Problem
- Überblick über die Interface-Syntax
- Interface-Assertion
- Formale Interface-Syntax
- Klassen und Interfaces
- Interface-bewusste Built-ins
- Abwärtskompatibilität
- Zusammenfassung der vorgeschlagenen Änderungen an Python
- Risiken
- Offene Fragen
- Abweichende Meinung
- Referenzen
- Urheberrecht
Hinweis
Die nicht mehr verfügbare Zope Interfaces Wiki-Seite (https://www.zope.org/Wikis/Interfaces), die ursprünglich hier verlinkt war und Links zu weiteren Ressourcen für dieses PEP enthielt, kann im Archiv der Wayback Machine gefunden werden. Außerdem wurde die Interface-Dev Zope Mailingliste, auf der dieses PEP diskutiert wurde, eingestellt, aber ihre Archive bleiben verfügbar.
Ablehnungsbescheid
Ich lehne dieses PEP ab. Es ist jetzt fünf Jahre her. Während ich erwarte, dass Python irgendwann Schnittstellen haben wird, wäre es naiv zu erwarten, dass sie der Syntax dieses PEP ähneln werden. Außerdem wird PEP 246 zugunsten von etwas völlig anderem abgelehnt; Schnittstellen werden keine Rolle bei der Anpassung oder dem, was sie ersetzen wird, spielen. GvR.
Einleitung
Dieses PEP beschreibt eine vorgeschlagene Syntax für die Erstellung von Interface-Objekten in Python.
Übersicht
Neben der Überlegung, ein statisches Typsystem zu Python hinzuzufügen, war die Types-SIG auch damit beauftragt, ein Interface-System für Python zu entwickeln. Im Dezember 1998 veröffentlichte Jim Fulton ein prototypisches Interfacesystem, das auf Diskussionen aus der SIG basierte. Viele der Probleme und Hintergrundinformationen zu dieser Diskussion und dem Prototyp finden sich in den SIG-Archiven [1].
Gegen Ende 2000 begann Digital Creations, über bessere Component-Model-Designs für Zope nachzudenken [2]. Das zukünftige Component-Model von Zope stützt sich stark auf Interface-Objekte. Dies führte zur Weiterentwicklung von Jims "Scarecrow"-Interface-Prototyp. Ab Version 2.3 wird Zope mit einem Interface-Paket als Standardsoftware geliefert. Das Interface-Paket von Zope wird als Referenzimplementierung für dieses PEP verwendet.
Die von diesem PEP vorgeschlagene Syntax basiert auf Syntaxverbesserungen, die in PEP 232 beschrieben werden, und beschreibt ein zugrunde liegendes Framework, auf dem PEP 233 basieren könnte. Es gibt einige Arbeiten in Bezug auf Interface-Objekte und Proxy-Objekte, daher möchten Sie sich für diese optionalen Teile dieses PEP möglicherweise [3] ansehen.
Das Problem
Interfaces sind wichtig, da sie eine Reihe von Problemen lösen, die bei der Softwareentwicklung auftreten.
- Es gibt viele implizite Interfaces in Python, die üblicherweise als "Protokolle" bezeichnet werden. Derzeit basiert die Bestimmung dieser Protokolle auf der Introspektion der Implementierung, aber oft schlägt auch dies fehl. Zum Beispiel impliziert die Definition von
__getitem__sowohl eine Sequenz als auch eine Zuordnung (erstere mit sequenziellen, ganzzahligen Schlüsseln). Es gibt keine Möglichkeit für den Entwickler, explizit anzugeben, welche Protokolle das Objekt implementieren möchte. - Python ist aus Sicht des Entwicklers durch die Trennung zwischen Typen und Klassen eingeschränkt. Wenn Typen erwartet werden, verwendet der Verbraucher Code wie `type(foo) == type("")`, um festzustellen, ob `foo` ein String ist. Wenn Instanzen von Klassen erwartet werden, verwendet der Verbraucher `isinstance(foo, MyString)`, um festzustellen, ob `foo` eine Instanz der Klasse `MyString` ist. Es gibt kein einheitliches Modell, um festzustellen, ob ein Objekt auf eine bestimmte, gültige Weise verwendet werden kann.
- Pythons dynamische Typisierung ist sehr flexibel und leistungsstark, aber sie hat nicht den Vorteil statisch typisierter Sprachen, die Typprüfung bieten. Statisch typisierte Sprachen bieten Ihnen viel mehr Typsicherheit, sind aber oft übermäßig wortreich, da Objekte nur durch gemeinsame Unterklassifizierung verallgemeinert und spezifisch mit Casting (z.B. in Java) verwendet werden können.
Es gibt auch eine Reihe von Dokumentationsproblemen, die Interfaces zu lösen versuchen.
- Entwickler verschwenden viel Zeit damit, den Quellcode Ihres Systems zu untersuchen, um herauszufinden, wie Objekte funktionieren.
- Entwickler, die neu in Ihrem System sind, können missverstehen, wie Ihre Objekte funktionieren, was zu Verwendungsfehlern führt und diese möglicherweise verbreitet.
- Da ein Mangel an Interfaces dazu führt, dass die Verwendung aus dem Quellcode abgeleitet wird, verwenden Entwickler möglicherweise Methoden und Attribute, die nur für den "internen Gebrauch" bestimmt sind.
- Code-Inspektion kann schwierig sein und Neulinge, die versuchen, von Gurus geschriebenen Code richtig zu verstehen, stark entmutigen.
- Viel Zeit wird verschwendet, wenn viele Leute sehr hart daran arbeiten, Obskurität (wie undokumentierte Software) zu verstehen. Investitionen in die Dokumentation von Interfaces im Voraus werden am Ende viel Zeit sparen.
Interfaces versuchen, diese Probleme zu lösen, indem sie Ihnen eine Möglichkeit bieten, eine vertragliche Verpflichtung für Ihr Objekt zu spezifizieren, Dokumentation darüber, wie ein Objekt verwendet wird, und einen eingebauten Mechanismus zur Entdeckung des Vertrags und der Dokumentation.
Python verfügt über sehr nützliche Introspektionsfunktionen. Es ist bekannt, dass dies die Erkundung von Konzepten im interaktiven Interpreter erleichtert, da Python Ihnen die Möglichkeit gibt, alle Arten von Informationen über Objekte abzurufen: den Typ, Docstrings, Instanz-Dictionaries, Basisklassen, ungebundene Methoden und mehr.
Viele dieser Funktionen sind auf die Introspektion, Nutzung und Änderung der Implementierung von Software ausgerichtet, und eine davon ("doc strings") ist auf die Bereitstellung von Dokumentation ausgerichtet. Dieser Vorschlag beschreibt eine Erweiterung dieses natürlichen Introspektionsframeworks, die das Interface eines Objekts beschreibt.
Überblick über die Interface-Syntax
Für den größten Teil ist die Syntax von Interfaces der Syntax von Klassen sehr ähnlich, aber zukünftige Bedürfnisse oder in Diskussionen aufgeworfene Bedürfnisse können neue Möglichkeiten für die Interface-Syntax definieren.
Eine formale BNF-Beschreibung der Syntax ist in diesem PEP später angegeben. Zur Veranschaulichung hier ein Beispiel für zwei verschiedene Interfaces, die mit der vorgeschlagenen Syntax erstellt wurden.
interface CountFishInterface:
"Fish counting interface"
def oneFish():
"Increments the fish count by one"
def twoFish():
"Increments the fish count by two"
def getFishCount():
"Returns the fish count"
interface ColorFishInterface:
"Fish coloring interface"
def redFish():
"Sets the current fish color to red"
def blueFish():
"Sets the current fish color to blue"
def getFishColor():
"This returns the current fish color"
Dieser Code erzeugt bei Auswertung zwei Interfaces namens CountFishInterface und ColorFishInterface. Diese Interfaces werden durch die interface-Anweisung definiert.
Die Prosa-Dokumentation für die Interfaces und ihre Methoden stammt aus Docstrings. Die Informationen zur Methodensignatur stammen aus den Signaturen der def-Anweisungen. Beachten Sie, dass es keinen Body für die `def`-Anweisungen gibt. Das Interface implementiert keinen Dienst für irgendetwas; es beschreibt lediglich einen. Dokumentationsstrings für Interfaces und Interface-Methoden sind obligatorisch; eine `pass`-Anweisung kann nicht bereitgestellt werden. Das Interface-Äquivalent einer `pass`-Anweisung ist ein leerer Docstring.
Sie können auch Interfaces erstellen, die andere Interfaces "erweitern". Hier sehen Sie einen neuen Interface-Typ, der CountFishInterface und ColorFishInterface erweitert.
interface FishMarketInterface(CountFishInterface, ColorFishInterface):
"This is the documentation for the FishMarketInterface"
def getFishMonger():
"Returns the fish monger you can interact with"
def hireNewFishMonger(name):
"Hire a new fish monger"
def buySomeFish(quantity=1):
"Buy some fish at the market"
Die FishMarketInterface erweitert die CountFishInterface und ColorFishInterface.
Interface-Assertion
Der nächste Schritt ist, Klassen und Interfaces zusammenzuführen, indem eine konkrete Python-Klasse erstellt wird, die behauptet, ein Interface zu implementieren. Hier ist ein Beispiel für eine FishMarket-Komponente, die dies tun könnte.
class FishError(Error):
pass
class FishMarket implements FishMarketInterface:
number = 0
color = None
monger_name = 'Crusty Barnacles'
def __init__(self, number, color):
self.number = number
self.color = color
def oneFish(self):
self.number += 1
def twoFish(self):
self.number += 2
def redFish(self):
self.color = 'red'
def blueFish(self):
self.color = 'blue'
def getFishCount(self):
return self.number
def getFishColor(self):
return self.color
def getFishMonger(self):
return self.monger_name
def hireNewFishMonger(self, name):
self.monger_name = name
def buySomeFish(self, quantity=1):
if quantity > self.count:
raise FishError("There's not enough fish")
self.count -= quantity
return quantity
Diese neue Klasse, FishMarket, definiert eine konkrete Klasse, die die FishMarketInterface implementiert. Das Objekt, das der implements-Anweisung folgt, wird als "Interface-Assertion" bezeichnet. Eine Interface-Assertion kann entweder ein Interface-Objekt oder ein Tupel von Interface-Assertionen sein.
Die in einer class-Anweisung wie dieser bereitgestellte Interface-Assertion wird im `__implements__`-Klassenattribut der Klasse gespeichert. Nach der Interpretation des obigen Beispiels hätten Sie eine Klassenanweisung, die mit einer `implements`-Built-in-Funktion wie folgt untersucht werden kann.
>>> FishMarket
<class FishMarket at 8140f50>
>>> FishMarket.__implements__
(<Interface FishMarketInterface at 81006f0>,)
>>> f = FishMarket(6, 'red')
>>> implements(f, FishMarketInterface)
1
>>>
Eine Klasse kann mehr als ein Interface realisieren. Wenn Sie beispielsweise ein Interface namens ItemInterface hätten, das beschreibt, wie ein Objekt als Element in einem Containerobjekt funktioniert. Wenn Sie behaupten möchten, dass FishMarket-Instanzen die ItemInterface sowie die FishMarketInterface realisieren, können Sie eine Interface-Assertion bereitstellen, die ein Tupel von Interface-Objekten für die FishMarket-Klasse enthält.
class FishMarket implements FishMarketInterface, ItemInterface:
# ...
Interface-Assertionen können auch verwendet werden, wenn Sie behaupten möchten, dass eine Klasse ein Interface implementiert und alle Interfaces, die eine andere Klasse implementiert.
class MyFishMarket implements FishMarketInterface, ItemInterface:
# ...
class YourFishMarket implements FooInterface, MyFishMarket.__implements__:
# ...
Diese neue Klasse YourFishMarket behauptet, dass sie die FooInterface sowie die von der MyFishMarket-Klasse implementierten Interfaces implementiert.
Es lohnt sich, etwas genauer auf Interface-Assertionen einzugehen. Eine Interface-Assertion ist entweder ein Interface-Objekt oder ein Tupel von Interface-Assertionen. Zum Beispiel:
FooInterface
FooInterface, (BarInterface, BobInterface)
FooInterface, (BarInterface, (BobInterface, MyClass.__implements__))
Sind alles gültige Interface-Assertionen. Wenn zwei Interfaces die gleichen Attribute definieren, ist die Reihenfolge, in der Informationen in der Assertion bevorzugt werden, von oben nach unten, von links nach rechts.
Es gibt andere Interface-Vorschläge, die aus Gründen der Einfachheit die Vorstellung von Klassen und Interfaces kombiniert haben, um eine einfache Interface-Durchsetzung zu ermöglichen. Interface-Objekte haben eine deferred-Methode, die eine verzögerte Klasse zurückgibt, die dieses Verhalten implementiert.
>>> FM = FishMarketInterface.deferred()
>>> class MyFM(FM): pass
>>> f = MyFM()
>>> f.getFishMonger()
Traceback (innermost last):
File "<stdin>", line 1, in ?
Interface.Exceptions.BrokenImplementation:
An object has failed to implement interface FishMarketInterface
The getFishMonger attribute was not provided.
>>>
Dies ermöglicht eine passive Interface-Durchsetzung, indem es Ihnen mitteilt, was Sie vergessen haben, um dieses Interface zu implementieren.
Formale Interface-Syntax
Die Python-Syntax wird in einer modifizierten BNF-Grammatiknotation definiert, die im Python Reference Manual beschrieben ist [4]. Dieser Abschnitt beschreibt die vorgeschlagene Interface-Syntax unter Verwendung dieser Grammatik.
interfacedef: "interface" interfacename [extends] ":" suite
extends: "(" [expression_list] ")"
interfacename: identifier
Eine Interface-Definition ist eine ausführbare Anweisung. Sie wertet zuerst die `extends`-Liste aus, falls vorhanden. Jedes Element in der `extends`-Liste sollte zu einem Interface-Objekt ausgewertet werden.
Die Suite des Interfaces wird dann in einem neuen Ausführungsrahmen ausgeführt (siehe Python Reference Manual, Abschnitt 4.1) unter Verwendung eines neu erstellten lokalen Namensraums und des ursprünglichen globalen Namensraums. Wenn die Suite des Interfaces die Ausführung beendet, wird ihr Ausführungsrahmen verworfen, aber ihr lokaler Namensraum wird als Interface-Elemente gespeichert. Ein Interface-Objekt wird dann unter Verwendung der `extends`-Liste für die Basisinterfaces und der gespeicherten Interface-Elemente erstellt. Der Interface-Name wird an dieses Interface-Objekt im ursprünglichen lokalen Namensraum gebunden.
Dieses PEP schlägt auch eine Erweiterung der `class`-Anweisung von Python vor.
classdef: "class" classname [inheritance] [implements] ":" suite
implements: "implements" implist
implist: expression-list
classname,
inheritance,
suite,
expression-list: see the Python Reference Manual
Bevor die Suite einer Klasse ausgeführt wird, werden die Anweisungen `inheritance` und `implements` ausgewertet, falls vorhanden. Das `inheritance`-Verhalten bleibt unverändert, wie in Abschnitt 7.6 des Language Reference definiert.
Die `implements`-Anweisung wird, falls vorhanden, nach `inheritance` ausgewertet. Diese muss zu einer Interfacespezifikation ausgewertet werden, die entweder ein Interface oder ein Tupel von Interfacespezifikationen ist. Wenn eine gültige Interfacespezifikation vorhanden ist, wird die Assertion als Tupel dem `__implements__`-Attribut des Klassenobjekts zugewiesen.
Dieses PEP schlägt keine Änderungen an der Syntax von Funktionsdefinitionen oder Zuweisungen vor.
Klassen und Interfaces
Die oben genannten Beispiel-Interfaces beschreiben keine Verhaltensweisen für ihre Methoden, sie beschreiben lediglich ein Interface, das ein typisches FishMarket-Objekt realisieren würde.
Sie bemerken vielleicht eine Ähnlichkeit zwischen Interfaces, die von anderen Interfaces erweitert werden, und Klassen, die von anderen Klassen unterklassifiziert werden. Dies ist ein ähnliches Konzept. Es ist jedoch wichtig zu beachten, dass Interfaces Interfaces erweitern und Klassen Klassen unterklassifizieren. Sie können keine Klasse erweitern oder ein Interface unterklassifizieren. Klassen und Interfaces sind getrennt.
Der Zweck einer Klasse ist es, die Implementierung der Funktionsweise eines Objekts zu teilen. Der Zweck eines Interfaces ist es, zu dokumentieren, wie mit einem Objekt gearbeitet wird, nicht wie das Objekt implementiert ist. Es ist möglich, mehrere verschiedene Klassen mit sehr unterschiedlichen Implementierungen zu haben, die dasselbe Interface realisieren.
Es ist auch möglich, ein Interface mit vielen Klassen zu implementieren, die Teile der Funktionalität des Interfaces mischen, oder umgekehrt ist es möglich, dass eine Klasse viele Interfaces implementiert. Aus diesem Grund sollten Interfaces und Klassen nicht verwechselt oder vermischt werden.
Interface-bewusste Built-ins
Eine nützliche Erweiterung der eingebauten Funktionen von Python im Hinblick auf Interface-Objekte wäre implements(). Dieses Built-in würde zwei Argumente erwarten, ein Objekt und ein Interface, und einen wahren Wert zurückgeben, wenn das Objekt das Interface implementiert, andernfalls falsch. Zum Beispiel:
>>> interface FooInterface: pass
>>> class Foo implements FooInterface: pass
>>> f = Foo()
>>> implements(f, FooInterface)
1
Derzeit existiert diese Funktionalität in der Referenzimplementierung als Funktionen im Interface-Paket, was ein "import Interface" erfordert. Ihre Existenz als Built-in wäre rein aus Bequemlichkeit und nicht notwendig für die Verwendung von Interfaces, und analog zu isinstance() für Klassen.
Abwärtskompatibilität
Das vorgeschlagene Interface-Modell führt keine Abwärtskompatibilitätsprobleme in Python ein. Die vorgeschlagene Syntax jedoch schon.
Jeglicher bestehender Code, der interface als Bezeichner verwendet, wird fehlschlagen. Es kann andere Arten von Rückwärtsinkompatibilitäten geben, die durch die Definition von interface als neues Schlüsselwort entstehen. Diese Erweiterung der Python-Syntax ändert keine bestehende Syntax in einer abwärtsinkompatiblen Weise.
Die neue from __future__ Python-Syntax (PEP 236) und das neue Warnungsframework (PEP 230) sind ideal, um diese Rückwärtsinkompatibilität zu lösen. Um Interface-Syntax jetzt zu verwenden, könnte ein Entwickler die Anweisung verwenden.
from __future__ import interfaces
Darüber hinaus wird jeder Code, der das Schlüsselwort interface als Bezeichner verwendet, eine Warnung von Python erhalten. Nach Ablauf der angemessenen Zeit wird die Interface-Syntax standardmäßig, die obige Import-Anweisung wird nichts bewirken und jeder als interface benannte Bezeichner wird eine Ausnahme auslösen. Dieser Zeitraum wird auf 24 Monate festgelegt.
Zusammenfassung der vorgeschlagenen Änderungen an Python
Hinzufügen des neuen Schlüsselworts interface und Erweiterung der Klassensyntax um implements.
Erweiterung der Klassenschnittstelle um __implements__.
Hinzufügen des Built-in `implements(obj, interface)`.
Risiken
Dieses PEP schlägt die Hinzufügung eines neuen Schlüsselworts zur Python-Sprache vor, interface. Dies wird Code brechen.
Offene Fragen
Ziele
Syntax
Architektur
Abweichende Meinung
Dieses PEP wurde noch nicht auf python-dev diskutiert.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0245.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT