Wie beschreibt man Schnittstellen bei Duck-Typing?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
deamon
User
Beiträge: 63
Registriert: Mittwoch 8. Oktober 2008, 11:14

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?
Leonidas
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.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

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.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

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")
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("...")
arbeiten, ähnliches trifft auf callables mit der callable() funktion zu.


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 ;)
lunar

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")
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.
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("...")
Nicht jedes iterierbare Objekt hat ein __iter__-Attribut.
Im Kern solltest du einfach nur prüfen ob ein objekt sich so verhalten kann wie benötigt.
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.

@deamon
Wenn dir Doku dir die Schnittstelle nicht ausreichend beschreibt: Use the source. Du kannst doch lesen, oder? ;)
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

Es geht ja nur um die Doku für die Schnittstellen.

Prüfen würde ich so gut wie nie explizit (mal abgesehen vllt vom Dispatchen, was ja selten nötig ist) sonden es einfach "probieren".
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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

Code: Alles auswählen

interface A { void m(); }
interface B { void m(); }
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:

Code: Alles auswählen

type dictlike<T>:
    def __getitem__(key: any) as T
    def __len__() as numeric
Stefan
BlackJack

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.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
BlackJack

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.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

lunar hat geschrieben:
Im Kern solltest du einfach nur prüfen ob ein objekt sich so verhalten kann wie benötigt.
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.

@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.
lunar

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.
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.
Leonidas
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
Antworten