Generische Funktionen mit Python

PEAK bietet ja seit geraumer Zeit generische Funktionen ala CLOS für Python an. Ich wollte immer mal damit rumspielen, aber lange Zeit war es ja einfach nur Bestandteil von PyProtocols, und die Installation etwas haarig. Seit September diesen Jahres ist es aber ausgekoppelt und sehr viel einfacher zu installieren. Also hab ich mich mal draufgestürzt.

Und ich muss sagen: wow. Was Phillip J. Eby da geleistet hat ist wirklich fantastisch. Die Integration in Python (funktioniert ab Python 2.3 - er hat einfach eine eigene Implementierung von Dekoratoren für Python 2.3 erfunden) ist super, auch wenn natürlich das eine oder andere etwas gewöhnungsbedürftig ist.

Ein kleines Beispiel:

import dispatch

[dispatch.generic()]
def anton(a,b):
    "handle two objects"

[anton.when('isinstance(a,int) and isinstance(b,int)')]
def anton(a,b):
    return a+b

[anton.when('isinstance(a,str) and isinstance(b,str)')]
def anton(a,b):
    return a+b

[anton.when('isinstance(a,str) and isinstance(b,int)')]
def anton(a,b):
    return a*b

[anton.when('isinstance(a,int) and isinstance(b,str)')]
def anton(a,b):
    return b*a

[anton.before('True')]
def anton(a,b):
    print type(a), type(b)

Dieses kleine Beispiel liefert einfach eine Funktion namens 'anton', welche auf Basis der Parametertypen unterschiedlichen Code ausführt. Das Beispiel ist natürlich völlig sinnfrei, zeigt aber einige wichtige Eigenschaften von generischen Funktionen:

  • generische Funktionen sind - anders als klassische Objekt/Klassenmethoden - nicht an irgendwelche Klassen oder Objekte fest gebunden. Statt dessen werden sie über ihre Parametertypen ausgewählt.
  • Parametertypen müssen demnach definiert werden - im Regelfall passiert das über eine Minisprache, mit der die Bedingungen der Auswahl formuliert werden. Das ist auch der einzige syntaktische Teil der mir nicht so gut gefällt: die Bedingungen werden als String abgelegt. Allerdings ist die Integration sehr gut, man erhält saubere Syntaxfehler schon beim Laden.
  • eine generische Funktion kann mit beliebigen Bedingungen überladen werden - nicht nur der erste Parameter ist entscheidend. Bedingungen können übrigens auch auf Basis von Werten entscheidungen treffen - jeder beliebige Python-Ausdruck kann dort verwendet werden.
  • mit Methodenkombinationen (Methoden sind hier die konkreten Ausprägungen einer generischen Funktion) kann man eine Methode vor oder nach ihrem Aufruf modifizieren, ohne an den Code selber ranzukommen. Das Beispiel benutzt eine before-Methode die immer (deshalb das 'True') herangezogen wird, um Debugging-Output zu erzeugen. Natürlich kann man bei before/after Methoden jederzeit auch Bedingungen benutzen, um sich auf spezifische Ausprägungen des Aufrufs der generischen Funktion zu hängen - womit generische Funktionen ein vollwertiges Event-System sind.

Ein recht guter Artikel über RuleDispatch (das generische Funktionen Paket) gibt es bei Developerworks.

Das Beispiel zeigt übrigens die Python 2.3 Syntax für Dekoratoren. Mit Python 2.4 kann natürlich auch die @-Syntax benutzt werden. Ein Nachteil soll nicht verschwiegen werden: die Definition von generischen Funktionen und ihren Methoden ist nicht interaktiv möglich - jedenfalls nicht mit der Python 2.3 Syntax. Leider muss man da generell mit externen Definitionen in Dateien arbeiten.

RuleDispatch wird definitiv einen Platz in meinem Werkzeugkasten finden - die Syntax ist einfach genug, die Möglichkeiten hingegen sind gigantisch. Als Eventsystem schlägt es an Flexibilität jedes andere System und als generelle Möglichkeit der Strukturierung des Codes kommt es sehr nah an CLOS heran. Schade, das Django sich vermutlich auf PyDispatch ausrichten wird - RuleDispatch würde meiner Meinung nach wesentlich besser passen (da viele Aspekte in Django als Dispatch auf mehrere Parametertypen geschrieben werden könnten).

tags: Programmierung, Python, Texte