Seite 1 von 1

Wie beschreibt man Schnittstellen bei Duck-Typing?

Verfasst: Mittwoch 7. Januar 2009, 18:46
von deamon
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?

Verfasst: Mittwoch 7. Januar 2009, 18:52
von Leonidas
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.

Verfasst: Donnerstag 8. Januar 2009, 10:28
von Darii
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.

Re: Wie beschreibt man Schnittstellen bei Duck-Typing?

Verfasst: Freitag 9. Januar 2009, 13:51
von Mad-Marty
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 ;)

Re: Wie beschreibt man Schnittstellen bei Duck-Typing?

Verfasst: Freitag 9. Januar 2009, 16:51
von 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? ;)

Verfasst: Freitag 9. Januar 2009, 16:56
von audax
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".

Verfasst: Samstag 10. Januar 2009, 12:16
von sma
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

Verfasst: Samstag 10. Januar 2009, 12:50
von 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.

Verfasst: Sonntag 11. Januar 2009, 09:55
von sma
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

Verfasst: Sonntag 11. Januar 2009, 12:11
von 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.

Re: Wie beschreibt man Schnittstellen bei Duck-Typing?

Verfasst: Montag 12. Januar 2009, 16:44
von Mad-Marty
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.

Re: Wie beschreibt man Schnittstellen bei Duck-Typing?

Verfasst: Montag 12. Januar 2009, 17:16
von 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.

Verfasst: Montag 12. Januar 2009, 19:02
von Leonidas
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".