In einer anderen Diskussion habe ich mich über die bei Python wegen Duck-Typing oft fehlenden Schnittstellenbeschreibungen beklagt. Dank Duck-Typing ist man nicht an eine harte Schnittstellen-Definition wie in Java gebunden. Aber eine Java-Schnittstelle dokumentiert nebenbei auch noch das erwartete Verhalten bzw. auch Werte. Wie sollte man denn die impliziten Duck-Typing-Schnittstellen in Python am besten dokumentieren? Und sagt mir jetzt bitte nicht, dass man sich den Quelltext angucken soll! Das ist zwar bei Python meistens noch erträglich, aber trotzdem allgemein eine Zumutung.
Wie sollte man also beschreiben, dass eine Methode bestimmte Parameter annehmen und etwas bestimmtes zurück liefern muss, damit Python weiß, dass es nach Watscheln und Gang eine Ente ist?
Wie beschreibt man Schnittstellen bei Duck-Typing?
-
- Python-Forum Veteran
- Beiträge: 16025
- Registriert: Freitag 20. Juni 2003, 16:30
- Kontaktdaten:
Du solltest eine Dokumentation schreiben, wo so etwas drinsteht, siehe etwa wie die durchaus populären PEP 249 (DB-API 2.0, was von jeder nennenswerten Datenbankschnittstelle unterstützt wird) und PEP 333 (WSGI, das von jedem nennenswertem Web-Framework unterstützt wird).
Und es gibt zum Dokumentieren auch eine Reihe von Tools, gerade in Docstrings ist reST zum Standardformat geworden und sie können mit ``help()`` ausgelesen werden, sowie von Programmen wie Sphinx verarbeitet werden.
Ich habe es mir in meinem Plugin-System einfach gemacht und einfach eine Basisklasse definiert die als Trait nutzbar ist und bei allem was noch implementiert werden muss Exceptions wirft. Wie es dann implementiert werden soll steht passenderweise dann gleich im Docstring.
Und es gibt zum Dokumentieren auch eine Reihe von Tools, gerade in Docstrings ist reST zum Standardformat geworden und sie können mit ``help()`` ausgelesen werden, sowie von Programmen wie Sphinx verarbeitet werden.
Ich habe es mir in meinem Plugin-System einfach gemacht und einfach eine Basisklasse definiert die als Trait nutzbar ist und bei allem was noch implementiert werden muss Exceptions wirft. Wie es dann implementiert werden soll steht passenderweise dann gleich im Docstring.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Weitere Möglichkeiten wärens die ABCs von Python 3 oder Zope Interfaces. Im Endeffekt bleibt dir auch bei Java nichts anderes übrig als in die Sourcen oder die Doku zu gucken. Von selbst implementiert sich das Interface auch nicht.
deamon hat geschrieben:In einer anderen Diskussion habe ich mich über die bei Python wegen Duck-Typing oft fehlenden Schnittstellenbeschreibungen beklagt. Dank Duck-Typing ist man nicht an eine harte Schnittstellen-Definition wie in Java gebunden. Aber eine Java-Schnittstelle dokumentiert nebenbei auch noch das erwartete Verhalten bzw. auch Werte. Wie sollte man denn die impliziten Duck-Typing-Schnittstellen in Python am besten dokumentieren? Und sagt mir jetzt bitte nicht, dass man sich den Quelltext angucken soll! Das ist zwar bei Python meistens noch erträglich, aber trotzdem allgemein eine Zumutung.
Wie sollte man also beschreiben, dass eine Methode bestimmte Parameter annehmen und etwas bestimmtes zurück liefern muss, damit Python weiß, dass es nach Watscheln und Gang eine Ente ist?
Wenn es sehr speziell ist kann sich ein "aushebeln" des duck typings durchaus lohnen.
Code: Alles auswählen
if not isinstance(x, MySuperSpecialClass):
raise TypeError("Expected a Blah received a Blubb")
Code: Alles auswählen
if __debug__:
if not hasattr(x, "__iter__"):
raise TypeError("...")
Im Kern solltest du einfach nur prüfen ob ein objekt sich so verhalten kann wie benötigt.
Bei einer Stringformattier-Funktion kannst du z.b. auf isinstance() BaseString prüfen.
Wenn du richtig aufwand reinstecken möchtest kannst du ja solche checks dir als Decorator anlegen und deine Funktionen per Decorator das prüfen lassen ... lohnt sich nur äusserst selten
Wenn sich die Funktion bei bestimmten Typen anders verhalten soll, ist das sicherlich sinnvoll. Ein Typtest, der nur dazu da ist, beim Fehlschlag einen Typfehler zu werfen, ist allerdings mehr oder weniger völlig überflüssig.Mad-Marty hat geschrieben:Wenn es sehr speziell ist kann sich ein "aushebeln" des duck typings durchaus lohnen.
Code: Alles auswählen
if not isinstance(x, MySuperSpecialClass): raise TypeError("Expected a Blah received a Blubb")
Nicht jedes iterierbare Objekt hat ein __iter__-Attribut.Wenn es sich nur um relativ simple container handelt die iterierbar sein müssen kannst du mit
Code: Alles auswählen
if __debug__: if not hasattr(x, "__iter__"): raise TypeError("...")
Man sollte gar nichts überprüfen. Wenn ein Objekt sich nicht wie erwartet verhält, gibt es früher oder später schon ein Ausnahme.Im Kern solltest du einfach nur prüfen ob ein objekt sich so verhalten kann wie benötigt.
@deamon
Wenn dir Doku dir die Schnittstelle nicht ausreichend beschreibt: Use the source. Du kannst doch lesen, oder?
Man unterscheidet zwischen nominellen und strukturellen Typsystemen.
Die meisten Programmiersprachen haben nominelle Typen, wo zwei Typen, die die selbe Struktur haben, immer noch unterschiedlich sind, wenn sie nicht den selben Namen haben. Java hat z.B. so ein Typsystem. Wenn ich dort
definiere, habe ich zwei unterscheidbare Typen erzeugt. "Ducktyping" sind strukturelle Typen. Diese sind mächtiger und ausdrucksstärker, allerdings für Computer und insbesondere Compilerbauer schwerer umzusetzen und daher in den wenigsten Sprachen vorhanden. Dabei entsprechen sie eher der Intuition und der mathematischen Sichtweise.
ECMAscript 4 sollte sie bekommen. Scala hat sie.
Der langen Vorrede kurzer Sinn: Wenn man Schnittstellen für Python beschreibt, sollte man in strukturellen Typensystemen denken und Typen (informell) so beschreiben.
Beispiel: Übergeben werden muss etwas, das sich "wie ein dict" verhält, genauer, welches __getitem__ und __len__ versteht.
Oder man denkt sich einen eigenen Formalismus aus:
Stefan
Die meisten Programmiersprachen haben nominelle Typen, wo zwei Typen, die die selbe Struktur haben, immer noch unterschiedlich sind, wenn sie nicht den selben Namen haben. Java hat z.B. so ein Typsystem. Wenn ich dort
Code: Alles auswählen
interface A { void m(); }
interface B { void m(); }
ECMAscript 4 sollte sie bekommen. Scala hat sie.
Der langen Vorrede kurzer Sinn: Wenn man Schnittstellen für Python beschreibt, sollte man in strukturellen Typensystemen denken und Typen (informell) so beschreiben.
Beispiel: Übergeben werden muss etwas, das sich "wie ein dict" verhält, genauer, welches __getitem__ und __len__ versteht.
Oder man denkt sich einen eigenen Formalismus aus:
Code: Alles auswählen
type dictlike<T>:
def __getitem__(key: any) as T
def __len__() as numeric
Wenn man den Typbegriff da ein wenig raushalten will, kann man auch von Protokollen sprechen. Also zum Beispiel, hier wird ein Objekt erwartet, welches das Sequenzprotokoll unterstützt. Und irgend wo beschreibt man dann, wie sich eine "Sequenz" zu verhalten hat.
Warum sollte man den Typbegriff raushalten wollen? Protokolle definieren IMHO strukturelle Typen. Und diese sind (so habe ich das mal gelernt) einfach Partitionen der Objektmenge, also Teilmengen, mit gemeinsamen Eigenschaften.
Stefan
Stefan
Um es den Leuten einfacher zu machen, die bei "Typ" immer erwarten, dass es da ein formale, in der Programmiersprache kodierte Schnittstelle oder abstakte Klasse zu geben muss. Was strukturelle Typen sind, hat halt nicht jeder gelernt.
lunar hat geschrieben:Man sollte gar nichts überprüfen. Wenn ein Objekt sich nicht wie erwartet verhält, gibt es früher oder später schon ein Ausnahme.Im Kern solltest du einfach nur prüfen ob ein objekt sich so verhalten kann wie benötigt.
@deamon
Wenn dir Doku dir die Schnittstelle nicht ausreichend beschreibt: Use the source. Du kannst doch lesen, oder?
Früher oder Später kannst du dich dann aber auch mal Totsuchen nach Fehlern. Es gibt durchaus Gründe zu prüfen. Ob es immer in einer exception Enden muss ist eine andere Frage.
Z.b. Strings anstelle einer Liste/Tuple kann zu schwer diagnostizierbaren Fehlern führen wenn die Werte erst sehr viel später verwendet werden.
Klar kann das zu schwer zu findenden Fehlern führen – wie eben alles andere auch. In der Realität allerdings findet man solche Fehler durch Unittests oder gezielt eingesetzte print-Statements.Mad-Marty hat geschrieben:Z.b. Strings anstelle einer Liste/Tuple kann zu schwer diagnostizierbaren Fehlern führen wenn die Werte erst sehr viel später verwendet werden.
-
- Python-Forum Veteran
- Beiträge: 16025
- Registriert: Freitag 20. Juni 2003, 16:30
- Kontaktdaten:
Unittests und insbesondere Doctests sind auch nicht schlecht um irgendwelche Vorgehensweisen im Code zu erklären also wie man die API nutzt. Das habe ich etwa bei Bazaar gemacht. "Use the unittests, Luke" ist oft einfacher als "Use the source, Luke".
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice