Text
                    Markus Nix (Hrsg.),
Martin Grimme, Torsten Marek,
Michael Weigend, Wolfgang Weitz
Exploring Python
3935042698_v01.book Seite 1 Freitag, 14. Oktober 2005 3:31 15


3935042698_v01.book Seite 2 Freitag, 14. Oktober 2005 3:31 15
Markus Nix (Hrsg.), Martin Grimme, Torsten Marek, Michael Weigend, Wolfgang Weitz Exploring Python Software-Entwicklung für Profis 3935042698_v01.book Seite 3 Freitag, 14. Oktober 2005 3:31 15
Markus Nix (Hrsg.), Martin Grimme, Torsten Marek, Michael Weigend, Wolfgang Weitz : Exploring Python Software-Entwicklung für Profis Frankfurt, 2005 ISBN 3-935042-69-8 © 2005 entwickler.press, ein Imprint der Software & Support Verlag GmbH http://www.entwickler-press.de http://www.software-support.biz Ihr Kontakt zum Verlag und Lektorat: lektorat@entwickler-press.de Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar. Korrektorat: Petra Kienle Satz: text & form GbR, Carsten Kienle Umschlaggestaltung: Melanie Hahn Belichtung, Druck und Bindung: M.P. Media-Print Informationstechnologie GmbH, Paderborn. Alle Rechte, auch für Übersetzungen, sind vorbehalten. Reproduktion jeglicher Art (Fotokopie, Nachdruck, Mikrofilm, Erfassung auf elektronischen Datenträgern oder andere Verfahren) nur mit schriftlicher Genehmigung des Verlags. Jegliche Haftung für die Richtigkeit des gesamten Werks kann, trotz sorgfältiger Prüfung durch Autor und Verlag, nicht übernommen werden. Die im Buch genannten Produkte, Waren- zeichen und Firmennamen sind in der Regel durch deren Inhaber geschützt. 3935042698_v01.book Seite 4 Freitag, 14. Oktober 2005 3:31 15
5 Inhaltsverzeichnis Foodamental........................................9 1GeneratoreninPython--TorstenMarek..................13 1.1Koroutinen&Continuations.......................15 1.1.1Koroutinen..............................15 1.1.2Continuations............................17 1.2Einführung&Übersicht...........................19 1.2.1 Generatorenschreiben&benutzen . . . . . . . . . . . 19 1.2.2DasIterator-Protokoll.....................24 1.2.3Generatorvariationen......................28 1.3Generatorausdrücke..............................30 1.4Dasitertools-Modul..............................34 1.5Anwendungsbeispiele............................37 1.5.1 Generatorklassen vs. Generatoren . . . . . . . . . . . . 38 1.5.2Iteratorennachrüsten......................40 1.5.3Microthreads............................41 1.6Erweiterungen..................................43 1.6.1Generatorattribute........................43 1.6.2Konsumenten............................46 1.6.3GeneratorenmitGreenlets..................47 1.7DieZukunft....................................51 1.7.1 PEP 342: "Coroutines via Enhanced Iterators" . . 51 1.7.2 PEP 343: "Anonymous Block Redux and GeneratorEnhancements"..................53 2 Objektorientierte Programmierung -- Michael Weigend . . . . . 57 2.1 Python -- eine objektorientierte Programmiersprache . . . . 57 2.2 Eine Klasse definieren -- die class-Anweisung . . . . . . . . . 58 2.3 Einführendes Beispiel: Modell einer Ampel . . . . . . . . . . . 60 2.4EineKlassetesten...............................61 3935042698_v01.book Seite 5 Freitag, 14. Oktober 2005 3:31 15
Inhaltsverzeichnis 6 2.5AttributeundMethodendefinieren..................63 2.5.1 Weiterführendes Beispiel: Modell eines Volumens...............................63 2.5.2 Initialisierung eines Objekts -- dieMethode__init__()....................65 2.6 KlassenattributeundObjektattribute. . . . . . . . . . . . . . . . . 65 2.7Methoden......................................68 2.8Vererbung......................................70 2.9 Mehrfachvererbung und Method Resolution Order . . . . . . 72 2.10Ableitenoderaggregieren?........................76 2.11Sichtbarkeit....................................80 2.11.1ÖffentlicheundprivateNamen. . . . . . . . . . . . . . 80 2.11.2 Properties: Attribute mit Zugriff über get/set-Methoden.........................81 2.12StatischeMethoden..............................85 2.13Iteratoren,iterierbareKlassen......................86 2.14KlassenfürunveränderbareObjekte.................89 2.15Introspektion...................................90 2.16Metaklassen....................................93 2.17Literatur.......................................96 3 AppletsbauenmitgDesklets--MartinGrimme. . . . . . . . . . . . 99 3.1ErsteSchritte...................................99 3.2DasFrameworkvongDesklets.....................101 3.3DieApplet-Beschreibungssprache...................102 3.3.1Hello-World-Beispiel......................102 3.3.2 AttributeundAction-Handler. . . . . . . . . . . . . . . 103 3.3.3Container-Elemente.......................105 3.3.4SonstigeElemente........................108 3.3.5Farbwerte...............................111 3.3.6Schriften................................111 3.3.7PfadeundURLs..........................112 3.4Layout........................................112 3.4.1Koordinateneinheiten......................113 3.4.2 Die Positionierungsverankerung . . . . . . . . . . . . . 114 3.4.3RelativePositionierung....................115 3935042698_v01.book Seite 6 Freitag, 14. Oktober 2005 3:31 15
Inhaltsverzeichnis 7 3.5EingebetteteSkripte..............................116 3.5.1SkripteimAction-Handler..................116 3.5.2DerDsp-Namensraum.....................117 3.5.3Das<script>-Tag.........................117 3.5.4VordefinierteFunktionen...................118 3.6Konfigurationsdialoge............................119 3.6.1EinbindenvonDialogen...................119 3.6.2Konfigurationswidgets.....................120 3.6.3DerPrefs-Namensraum....................124 3.7Controlseinbinden...............................125 3.7.1Schnittstellen............................125 3.7.2DerControl-Inspector.....................125 3.7.3Controlsverwenden.......................127 3.8Controlsschreiben...............................128 3.8.1Schnittstellendefinition....................128 3.8.2 ImplementierenvonAttributen. . . . . . . . . . . . . . 129 3.8.3DieControl-Basisklasse....................131 3.8.4 DasTestskripttest-control.py. . . . . . . . . . . . . . . 131 4 PythonunddieJava-Welt--WolfgangWeitz . . . . . . . . . . . . . . 133 4.1Jython.........................................133 4.1.1Vorbereitungen...........................134 4.1.2Jythoninteraktiv..........................135 4.1.3 Ereignisbehandlung und Eigenschaften . . . . . . . . 136 4.1.4 Beispiel: Ein Überraschungs-Mediaplayer . . . . . 140 4.1.5 Einbetten von Jython in Java-Programme . . . . . . 147 4.1.6 Kompilieren von Jython-Skripten . . . . . . . . . . . . 152 4.1.7AusblickaufJython2.2....................154 4.2JPype.........................................155 4.3KommunikationüberXML-RPC....................157 4.3.1 ZugriffaufXML-RPC-Dienste. . . . . . . . . . . . . . 158 4.3.2 Anbieten von XML-RPC-Diensten . . . . . . . . . . . 162 4.4 WelcheAlternativeistnundiebeste?. . . . . . . . . . . . . . . . 165 Stichwortverzeichnis.................................167 DieAutoren.........................................173 3935042698_v01.book Seite 7 Freitag, 14. Oktober 2005 3:31 15
3935042698_v01.book Seite 8 Freitag, 14. Oktober 2005 3:31 15
9 Foodamental Die Grundlagen von Python sind schnell umrissen: Syntaktische Zweideu- tigkeiten sollen vermieden werden, die Formatierung wird zur Grundlage lesbaren (und funktionierenden) Programmcodes. Python lässt sich relativ einfach in andere Sprachen einbetten und eignet sich hervorragend, um vor- handene Sprachen zu erweitern. Als glue language hält Python, was viele Skriptsprachen versprechen: Rapid Application Development. Python ist in gewisser Weise eine funktionierende Proto-Sprache. Oder lyrisch ausge- drückt: Auch wenn ich PHP oder Java programmiere, so träume ich doch in Python. Trotz extremer Einfachheit des Sprachkerns sind Entwickler aber keineswegs auf einen bestimmten Entwicklungsstil festgelegt. Ob Objekt- orientierung, Funktionale Programmierung, Aspektorientierte Entwicklung oder Design by contract: Python ist auf allen Gebieten zuhause, verweigert sich aber dennoch einer barocken Syntax, die z.B. Perl für viele so unattrak- tiv macht. Die Attribute, die man der Sprache auf Tagungen und in Mailing- listen gibt, sind stets dieselben: Python sei "schön", "einfach" und "eindeu- tig". Python-Entwickler sind die Surfer unter den Programmierern: schnell, wendig, mit ausgeprägtem Hang zum Spaß. Diese spielerische Herange- hensweise zeigt sich auch in Tutorials und Dokumentationen. Python hat viele Grundlagen. Von ABC stammen Intendation-Prinzip und Teile der Objekt-Implementierung, von Modula Exception Handling, Mo- dul-Mechanismus und das Schlüsselwort self in der Parameterliste. String- Slicing wurde Algon-68 entliehen, viele der Schlüsselwörter und die Opera- torenpriorität sind aus C bekannt. Die einfache Erweiterbarkeit von Python macht die Sprache selbst zu einem leistungsfähigen Staubsauger und zu ei- nem Allheilmittel in einer sich zunehmend balkanisierenden Software-Welt. Dass Python trotz exzellenter Referenzen wie Google, Nokia oder der NASA bis heute nicht die Popularität von PHP erreichen konnte, hat aus meiner Sicht einen einfachen Grund. Als das Netz populär wurde, war Py- thon längst in anderen Zusammenhängen etabliert, in der Systemadminis- tration etwa oder in der Mathematik. Das Web wurde nicht zum Motor der Entwicklung, der Faktor Community wuchs langsamer, als es z. B. bei PHP 3935042698_v01.book Seite 9 Freitag, 14. Oktober 2005 3:31 15
Foodamental 10 der Fall war. So ist Python trotz der extrem einfachen Syntax zur Sprache der Wissenden geworden. Python-Programmierer sind dekadent, weil sie bereits am Mittag ihre Arbeit erledigt haben können. Python ist bekannt genug, um eine emsige Entwicklergemeinde um sich zu scharen, unbekannt genug, um eine Entwicklung zu durchlaufen, die (wie PHP) einem Innovationsdruck unterliegt, der in extrem kurzen Entwick- lungszyklen nichtige Verbesserungen bringt. Als "wohlwollender Diktator auf Lebenszeit" garantiert Guido van Rossum uns haltlosen Seelen jene Sta- bilität, die wir zu unserer täglichen Arbeit brauchen. PEP 1 [1] verrät, wel- che Zyklen ein Verbesserungsvorschlag durchlaufen muss, bevor er zur Re- alität des Entwicklers wird. Now to something completely different Python 3000, bedrohliche Chimäre über vermeintlichem Legacy-Code, existiert nur noch als rhetorische Figur. Wir dürfen uns zurücklehnen und uns entspannt der inkrementellen Verbesserung einer Sprache erfreuen, die viele von uns als die beste aller Sprachen verstehen. Python 3000 hätte -- na- türlich -- on the fly den kompletten Sourcecode auf unserer Platte analysiert und optimiert, aber was wir mit Python 2.x haben, versetzt dennoch die Ver- treter anderer Sprachen in Staunen. Ob Systemadministration, Web-Frame- work oder 3D-Modelling, es gibt kaum Bereiche, in denen Python nicht sei- ne Stärken entfalten kann. Dieses Buch ist in seiner "kurzweiligen Tiefgründigkeit" ein konzeptionel- les Novum. Es sind Spezialistenbeiträge, die in ihrer Tiefe einen Zeitschrif- tenartikel sprengen würden, jedoch im Rahmen eines umfangreicheren Bu- ches der Forderung nach Aktualität nicht nachkommen könnten. In diesem Buch kommen Macher und ambitionierte Nutzer gleichermaßen zu Wort. Martin Grimme, Autor von gDesklets, Professor Wolfgang Weitz, Vorreiter der Python-Nutzung im universitären Bereich, Michael Weigend, Buchautor mit Hang zur Objektorientierten Programmierung sowie Torsten Marek, Py- thonista mit umfassender Kenntnis zentraler Python-Projekte. Die Vielfalt der Themen und die Eleganz der Implementierungen ist dabei beredter Be- weis der Leistungsfähigkeit von Python. Ob als Entwicklungsumgebung für Desktop Applets (Martin Grimme) oder als Java-Implementierung (Wolf- 3935042698_v01.book Seite 10 Freitag, 14. Oktober 2005 3:31 15
Foodamental 11 gang Weitz): Python etabliert sich mehr und mehr zur lingua franca ambi- tionierter Entwicklungsprojekte. Gründe hierfür finden Sie auch in den Beiträgen von Michael Weigend (der ausgesprochen clevere Prinzipien ob- jektorientierter Programmierung vorstellt) und Thomas Marek (der ausge- sprochen raffinierte Prinzipien der Python-Entwicklung erklärt). Es ist ein häufig kolportiertes Faktum, dass Python seinen Namen in erster Linie nicht der gleichnamigen Schlange verdankt, sondern vielmehr der englischen Komikertruppe Monty Python. Wir sollten dennoch nicht ver- gessen, dass die Python vor allem eine listige Schlange ist: leise, schnell, wendig. Potsdam, im August 2005 Markus Nix [1] http://www.python.org/peps/pep-0001.html 3935042698_v01.book Seite 11 Freitag, 14. Oktober 2005 3:31 15
3935042698_v01.book Seite 12 Freitag, 14. Oktober 2005 3:31 15
13 1 Generatoren in Python Von Torsten Marek Die kürzeste Beschreibung für Generatoren lautet "Funktionen mit mehreren Rückgabewerten". Diese Beschreibung ist natürlich irreführend, denn ein Generator ist eine Funktion, die zwischen zwei Aufrufen den internen Aus- führungszustand speichert und beliebig viele Werte zurückgeben kann. Wie Generatoren in Python intern funktionieren, wie man sie für andere Dinge als Iteration einsetzt und bestehende Beschränkungen umgeht, davon handelt dieser Artikel. Das Konzept der Generatoren ist Koroutinen entlehnt und bezeichnet einen Typus von Flusskontrolle, der in den "etablierten" Programmiersprachen von heute kaum verbreitet ist. Die theoretischen Grundlagen, die in Kapitel 1.1 behandelt werden, sind aber keinesfalls neu, und viele Sprachen beinhal- ten eine Implementation von Generatoren oder Koroutinen. Zu Python wur- den sie in der Version 2.2 hinzugefügt, seit Python 2.3 stehen sie standard- mäßig zur Verfügung. Seitdem wurden viele Funktionen zur Benutzung mit Generatoren hinzugefügt, zum Beispiel das Modul itertools, das in Kapitel 1.4 vorgestellt wird. Weitere Verbesserungen an der Implementation sind geplant und werden am Ende dieses Artikels besprochen. Die Syntax in Python ist vom Generatormechanimus in der Programmiersprache Icon (http://www.cs.arizona.edu/icon/) inspiriert. In Icon sind Generatoren ein -- wenn nicht das zentrale -- Sprachelement. Es lohnt sich, nach der Lektüre dieses Artikels die Beispiele und Dokumentationen auf der Webseite durch- zulesen, denn die Generatoren in Icon gehen weit über die in Python hinaus. Aber Python ist natürlich nicht die einzige, heute weit verbreitete Sprache mit Generatoren. Ruby hat Generatoren und das "neue" C# 2.0 bekommt sie auch. Im Folgenden ist das gleiche einfache Beispiel jeweils in der Syntax von Icon, Python und C# 2.0 dargestellt. 3935042698_v01.book Seite 13 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 14 Ein sehr einfacher Generator in Icon Der gleiche Generator in Python Der gleiche Generator in C# 2.0 Zu den Codebeispielen Die Codebeispiele wurden für Python 2.4 geschrieben, so weit es im Text nicht anders vermerkt ist. Die meisten sind auch zu Python 2.3 kompatibel, wenn sie keine Generatorausdrücke oder Methoden-Dekoratoren benutzen. Für das Verständnis der Codebeispiele und der darin benutzten Standard- funktionen und -module ist es sehr hilfreich, den interaktiven Python-Inter- preter zu benutzen. Noch komfortabler als mit dem Python-Interpreter aus der Python-Distribution geht es mit IPython (http://ipython.scipy.org/), PyCrust (http://www.wxpython.org), der Python-Konsole in IDLE (Teil der Python-Distribution) oder eric3 (http://eric.sourceforge.net). Alle hier ge- nannten Programme unterstützen syntaktische Einfärbung des Programm- codes in der Kommandozeile, umfassen Möglichkeiten zur Introspektion, procedure CityGenerator() suspend "New York" suspend "Rio" suspend "Tokio" end def CityGenerator(): yield "New York" yield "Rio" yield "Tokio" public class CityCollection : IEnumerable<string> { public IEnumerator<string> GetEnumerator() { yield "New York"; yield "Rio"; yield "Tokio"; } } 3935042698_v01.book Seite 14 Freitag, 14. Oktober 2005 3:31 15
Koroutinen & Continuations 15 bieten Zugriff auf die eingebaute Quellcodedokumentation in Python und sind mächtiger und komfortabler als die Eingabezeile des Standardinterpre- ters. Die meisten dieser Programme sind sowohl für Windows als auch für viele Unix-Derivate und MacOS X verfügbar. Die Codebeispiele finden sich, sofern sie über einen Dateinamen in der ers- ten Zeile verfügen, auch auf der Webseite des Buchs. 1.1 Koroutinen & Continuations Bevor wir uns mit den Generatoren von Python beschäftigen, ist es hilfreich, sich mit der Idee der Flusskontrolle vertraut zu machen, die den Generatoren zu Grunde liegt. Der folgende Abschnitt enthält eine kurze Erklärung der Koroutinen und Continuations. In diesem Kapitel behandeln wir die Be- ziehung zwischen Generatoren und Koroutinen, aber auch Koroutinen in Python. Deshalb ist ein grundlegendes Verständnis förderlich. 1.1.1 Koroutinen Denkt man an strukturierte oder objektorientierte Programmiersprachen, so ist der Aufrufmechanismus für Methoden oder Funktionen immer stack- basiert. Eine Funktion a wird, wenn sie eine weitere Funktion b aufruft, für deren Laufzeit "suspendiert". Nach dem Ende von b wird a weiter ausge- führt, bis auch sie beendet ist (sieht man von Signalen oder einem Programm- ende ab). Bei ihrem nächsten Aufruf können diese Funktionen nur mithilfe von statischen Variablen (so weit sie von der verwendeten Sprache unter- stützt werden) oder Variablen außerhalb der Funktion feststellen, ob sie schon einmal aufgerufen wurden. Wenn das Ergebnis des letzten Aufrufs den neuen Aufruf beeinflussen soll, muss man ebenfalls darauf zurückgreifen. Denn, so trivial es sich anhören mag, eine Funktion beginnt bei einem Aufruf mit der Ausführung immer am Anfang. Denkt man aber einen Schritt weiter, erscheint diese Aussage nicht mehr so selbstverständlich. Warum sollte eine Rücksprunganweisung nicht die gleiche Semantik haben können wie ein Funktionsaufruf? Damit wären beide Anweisungen eigentlich nur verschie- dene Bezeichnungen für den gleichen Vorgang, nämlich die Kommunikation mit einer anderen Funktion. Sowohl bei einem Aufruf als auch bei einem Rücksprung werden ja nur Daten an eine andere Funktion übergeben. 3935042698_v01.book Seite 15 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 16 Genau diese Art von Flusskontrolle haben Koroutinen, die in einem Pro- gramm "nebeneinander" laufen. Die Kontrolle über die Ausführung wird zwischen den Koroutinen herumgereicht. Daher spricht man auch nicht vom Aufruf, sondern von der Aktivierung einer Koroutine. Traditionelle Funk- tionen bzw. Subroutinen sind dann lediglich Spezialfälle, in denen die Aus- führung immer am Anfang startet, wenn die Funktion aktiviert bzw. aufge- rufen wird. Der komplette Zustand eines Programms wird also bei der Verwendung von Koroutinen nicht vom Stack (vergessen wir für diese Zeit einmal den Heap und dynamische Speicherallokation) mit den Stackframes aller aufgerufe- nen Funktionen bestimmt, sondern vom internen Zustand der Koroutinen. Im folgenden Codebeispiel sehen wir einige Koroutinen, die zusammen die Codezeilen aller Python-Quelldateien in einem Verzeichnisbaum berech- nen. Ungewöhnlich ist, dass die Ausführung mit der Funktion countLines() beginnt, die sich dann die Namen aller Python-Dateien holt. Normalerweise würde man erwarten, dass zuerst die Dateinamen mit walkTree() gelesen und anschließend verarbeitet werden. import os import sys locs=0 root = sys.argv[1] def walkTree(): global root for dir, dirs, files in os.walk(root): for filename in files: getPythonFiles.switch(filename) def getPythonFiles(): while True: filename = walkTree.switch() if filename.endswith(".py"): countLines.switch(filename) ... 3935042698_v01.book Seite 16 Freitag, 14. Oktober 2005 3:31 15
Koroutinen & Continuations 17 Es ist zu beachten, dass die Aufrufe von switch() nur die Kontrolle weiter- reichen. Ob eine Koroutine nach einem Kontextwechsel die Kontrolle zu- rückerhält und von welcher Koroutine sie kommt, ist nicht spezifiziert. Die Koroutinen bestimmen also selbst die weitere Ausführung des Programms und die Ausführung der Funktion beginnt an dem Punkt, wo der letzte Da- tentransfer, also der letzte switch()-Aufruf, stattgefunden hat. So wie man "normale" Funktionen bzw. Subroutinen mit einem Stack im- plementiert, so benutzt man für die Implementation von Koroutinen so ge- nannte Continuations, die wir hier nur kurz besprechen werden. 1.1.2 Continuations Eine Continuation enthält den kompletten Zustand eines Programms zu ei- nem gegebenen Zeitpunkt, d.h. die komplette Zukunft eines Programms. Da man beliebig zwischen den verschiedenen Continuations hin- und hersprin- gen kann, lassen sich mit ihrer Hilfe alle Variationen von Flusskontrolle rea- lisieren. Daher erfolgt auch meistens der Hinweis, dass Continuations we- gen ihrer Komplexität den Implementatoren einer Sprache vorbehalten bleiben sollen, die mit ihrer Hilfe spezifischere Anweisungen bauen können, eben die Koroutinen. Eine sehr einfache Anwendung von Continuations sind "escaping continua- tions", die unter bestimmten Umständen eine lange Berechnung abbrechen können, um an einen vorher bestimmten Punkt im Programm zurückzukeh- ren. Dieses Verhalten lässt sich mit Hilfe von Ausnahmen in Python (nur) si- mulieren. Zwar muss bei Ausnahmen der Stack aufgelöst werden, was bei Continuations nicht der Fall ist (der im Sinne eines C-Stack gar nicht exis- tiert), außerdem können dazwischen befindliche finally-Blöcke weiteren def countLines(): global locs while True: filename = getPythonFiles.switch() locs += len(file(filename).readlines()) countLines.switch() print locs 3935042698_v01.book Seite 17 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 18 Code ausführen. Als Beispiel wählen wir eine Tiefensuche in einem binären Baum (ein beliebtes und eingängiges Beispiel). Wird das gesuchte Element gefunden, wird eine FoundException-Ausnahme ausgelöst. Daher muss der Aufruf von search() in einem try-Block stehen, hinter dem diese Ausnah- me abgefangen werden kann. Auch wenn Ausnahmen in Python häufiger benutzt werden (und werden sollen), ist die Benutzung für die nicht-lokale Programmsteuerung nicht zu empfehlen. Bei einer Ausnahme in Python kann man prinzipbedingt nicht wissen, ob der Programmierer einer aufrufenden Funktion an das Abfangen der Ausnahme gedacht hat. Hierfür wäre ein Mechanismus wie die throws- Annotationen in Java notwendig. Aber selbst, wenn das Abfangen der Aus- nahme garantiert würde, ist von dieser Art zu programmieren abzuraten. # nonlocalexit.py class FoundException(Exception): pass label = lambda x: x[0] left = lambda x: len(x) > 1 and x[1] or None right = lambda x: len(x) > 1 and x[2] or None def search(node, value, level = 0): if node is None: return if label(node) == value: raise FoundException else: search(left(node), value, level + 1) search(right(node), value, level + 1) try:tree = ... search(tree, 7) except FoundException: print "Found!" else:print "Not found!" 3935042698_v01.book Seite 18 Freitag, 14. Oktober 2005 3:31 15
Einführung & Übersicht 19 Schließlich ist die Flusskontrolle hier nicht offensichtlich. Dies ist der Grund, warum sich einige Programmierer überhaupt gegen Ausnahmen aus- sprechen, denn sie verstecken Flusskontrolle, ähnlich C-Präprozessorma- kros, die zur Flusskontrolle benutzt werden. Einige weiterführende Artikel zu diesem Thema finden sich unter http://blogs.msdn.com/oldnewthing/ archive/2005/01/06/347666.asp und http://www.joelonsoftware.com/items/ 2003/10/13.html (beide in Englisch). Bei Continuations ist das nicht der Fall, denn ihre Verwendung muss in diesem Fall explizit angegeben werden und es gibt keine Unsicherheit in Bezug auf den Punkt, an dem das Pro- gramm weiter ausgeführt wird. Für das tiefere Verständnis von Continuations ist die Lektüre der zahlrei- chen Beispiele und Erklärungen im Internet hilfreich, zusammen mit einem Scheme-Interpreter. Einige Links: http://rubygarden.org/ruby?ContinuationExplanation http://c2.com/cgi/wiki?CallWithCurrentContinuation http://c2.com/cgi/wiki?ContinuationExplanation http://www.madore.org/~david/computers/callcc.html 1.2 Einführung & Übersicht 1.2.1 Generatoren schreiben & benutzen Obwohl die Generatoren in Python von Koroutinen inspiriert sind, unter- scheiden sie sich in einigen Kernpunkten von richtigen Koroutinen. Zum ei- nen können Generatoren nicht einen beliebigen anderen Generator "aktivie- ren", ohne dass sie danach die Kontrolle zurückbekommen, wie es bei Koroutinen der Fall ist. Eine Funktion oder ein Generator, die bzw. der einen Generator aufruft, erhält also auch immer die Kontrolle über das Programm zurück. Das gilt selbst dann, wenn in der aufgerufenen Funktion eine Aus- nahme auftritt. Denn es wird geprüft, ob diese Ausnahme im Kontext der aufrufenden Funktion abgefangen werden kann oder ob es noch finally- Blöcke gibt, die ausgeführt werden müssen. Der Calltree eines Python-Pro- gramms ist daher immer streng hierarchisch. Der Unterschied zu echten Koroutinen ist dadurch recht groß. Aber wie schon bei den Koroutinen erwähnt, ist dieses Programmierparadigma, so 3935042698_v01.book Seite 19 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 20 praktisch es für manche Aufgaben ist, nur wenig bekannt. Daher kann es von vielen Programmierern auch gar nicht vermisst werden. Allerdings gibt es eine C-Erweiterung für Python selbst, die richtige Koroutinen bereitstellt. Diese Greenlets betrachten wir später in diesem Kapitel noch genauer. Die andere Restriktion, auf die man in der Praxis immer wieder stößt, ist die fehlende Möglichkeit, bei einem erneuten Aufruf eines Generators zusätzli- che Daten in den laufenden Code einzuschleusen, denn yield ist, im Gegen- satz zu switch(), asymmetrisch. Die Bezeichnung "Generator" kommt ja gerade daher, dass man bei der aktuellen Implementation nur Werte aus dem Generator herausholen kann. Als "Konsumenten", die beliebig viele Werte übernehmen und intern verarbeiten, sind Generatoren bisher nicht gedacht. Es gibt mehrere Ansätze, Unterstützung dafür in Python einzubauen, und für eine der nächsten Python-Versionen ist eine solche Erweiterung geplant, die in Kapitel 1.7 vorgestellt wird. Allerdings lassen sich jetzt schon mit etwas Python-Code Konsumenten schreiben. Zwar können sie syntaktisch nicht so elegant sein, sie bieten aber fast die gleichen Möglichkeiten und lassen sich einfach erweitern. Den Code dazu gibt es in Kapitel 1.6.2. Der Lebenszyklus eines Generators: Anfang ... Das Schreiben von Generatoren ist denkbar einfach: Enthält der Code einer Funktion eine oder mehrere yield-Anweisungen, wird aus ihr vom Byte- code-Compiler ein Generator erstellt, unabhängig davon, ob es sich um eine Klassenmethode oder um eine freie Funktion handelt. Das folgende Beispiel zeigt einen sehr einfachen Generator, der zwei Zeichenketten zurückliefert. Ruft man einen Generator wie eine Funktion auf, wird zuerst nur eine Instanz des Generators erstellt und ein Objekt geliefert, mit dem man die Ausführung des Generators kontrollieren kann. Das Objekt verhält sich wie eine Sequenz und kann folglich in allen Kontexten, die eine Sequenz erlauben bzw. er- fordern, benutzt werden, also in einer for-Schleife oder auch mit map(), re- # einfuehrungsbeispiele.py def blumen(): yield "Osterglocke" yield "Geranie" 3935042698_v01.book Seite 20 Freitag, 14. Oktober 2005 3:31 15
Einführung & Übersicht 21 duce() und filter(). Wichtig ist übrigens, zwischen dem Generator und ei- ner Instanz des Generators zu unterscheiden. Die Generatorinstanz bezieht sich immer auf einen laufenden Generator. Wo es unmissverständlich ist, wird aber auch die Bezeichnung Generator für Generatorinstanz benutzt. Beim ersten Aufruf der Methode next() der Generatorinstanz beginnt die Ausführung des Codeblocks. Der Aufruf kann explizit im Code erfolgen, in einer for-Schleife ist er implizit. Erreicht die Ausführung des Programms ein yield, wird das Argument wie bei return zurückgegeben. Bei einer Funktion erfolgt danach die Auflösung des Stackframe. Dadurch werden alle Referenzen auf lokale Variablen der Funktion ungültig und diese wer- den vom Gargabe Collector entfernt. Bei einem Generator wird der Stack- frame nur vom C-Stack des Interpreters genommen und in einem Objekt ge- speichert. Dadurch bleibt die komplette Information über den Zustand des Generators in der Virtual Machine erhalten. Beim nächsten Aufruf dieser In- stanz des Generators wird dieses Objekt benutzt, um den Stackframe der Funktion auf dem C-Stack zu restaurieren. Der Kontrollfluss setzt dann un- mittelbar hinter der letzten yield-Anweisung ein. Das folgende Codebeispiel zeigt einige unterschiedliche Kontexte, in denen Generatoren benutzt werden können. Zusätzlich zu next() hat eine Generatorinstanz neben der üblichen Anzahl magischer Funktionen noch die Attribute gi_running und gi_frame. Das Attribut gi_running zeigt, ob ein Generator gerade ausgeführt wird, denn eine Generatorinstanz kann nicht mehr als einmal gleichzeitig laufen. Der folgende Beispielcode löst daher eine Ausnahme aus, denn hier versucht die Generatorinstanz, sich selbst aufzurufen. # einfuehrungsbeispiele.py, Fortsetzung blumenGen = blumen() print blumenGen.next() print blumenGen.next() for blume in blumen(): print blume print "\n".join(blumen()) 3935042698_v01.book Seite 21 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 22 Beim Aufruf von kInst.next() innerhalb von k ist kInst nicht suspendiert. Ein Einsprungpunkt für einen erneuten Aufruf existiert aber nur nach einem yield. Das ist hier nicht der Fall, denn vorher muss erst kInst.next() been- det sein. Allerdings gilt diese Beschränkung nur für gleiche Instanzen eines Generators. Rekursive Generatoren, die eine neue Instanz von sich aufrufen, sind genauso üblich wie rekursive Funktionen. Das einzige, was nicht funk- tioniert, ist das Weiterspringen zum nächsten Wert, den der Generator erzeu- gen würde. Das Attribut gi_frame enthält den momentanen Ausführungszustand des Generators, so z.B. die lokalen und globalen Variablen und weitere Informa- tionen über die Funktion in func_code. In gi_frame.f_lineno steht die Zei- lennummer des Einsprungpunkts beim nächsten Aufruf. Zwar mag dieses Attribut von zweifelhafter Praxisbedeutung im Alltag sein, aber so lässt sich noch einmal ganz genau nachvollziehen, wie der Generator ausgeführt wird. # gi_running.py def k(): print kInst.gi_running yield kInst.next() kInst = k() kInst.next() # f_lineno.py def blumen(): yield "Zeile 1" yield "Zeile 2" yield "Zeile 3" yield "Zeile 4" try:b = blumen() while True: print "Wert aus %s" % (b.next(), ) print "Ruecksprung zu Zeile", b.gi_frame.f_lineno except StopIteration: pass 3935042698_v01.book Seite 22 Freitag, 14. Oktober 2005 3:31 15
Einführung & Übersicht 23 ... und Ende Auch die Semantik von return innerhalb eines Generators ändert sich. Die Anweisung kann keine Daten zurückgeben (sie muss also immer einzeln auf einer logischen Codezeile stehen), sondern signalisiert, dass der Generator keine weiteren Werte mehr produzieren kann. Stattdessen wird eine Stop- Iteration-Ausnahme ausgelöst, die diesen Zustand an den aufrufenden Code übermittelt. Am Ende eines Generator-Codeblocks ist eine return- Anweisung impliziert, wie in einer Funktion auch. Tritt bei der Ausführung des Generators eine unbehandelte Ausnahme auf, können keine weiteren Werte mehr produziert werden. Beim nächsten Aufruf wird deshalb auch eine StopIteration-Ausnahme ausgelöst, denn der Zustand im Generator ist undefiniert. Natürlich kann eine Generatorinstanz auch jederzeit vom Gar- bage Collector gelöscht werden. Das passiert dann, wenn alle Referenzen auf eine Instanz ungültig werden. Der eingefrorene Stackframe wird dann entfernt, ohne dass die Generatorinstanz davon benachrichtigt wird, um eventuelle Aufräumarbeiten auszuführen. Auch dieses Verhalten wird sich in Zukunft ändern, wie wir in Kapitel 1.7.1 sehen werden. Das folgende Codebeispiel zeigt die verschiedenen Möglichkeiten, wie sich die Ausführung eines Generators stoppen lässt. Alle Funktionen, die einen Generator als Argument akzeptieren, fangen die StopIteration-Ausnahme selbstverständlich ab. Für die for-Schleife gilt das Gleiche und Ausnahmen aus einem Generator verhalten sich wie erwartet. # einfuehrungsbeispiele.py, Fortsetzung def blumen2_mitReturn(): yield "Rose" yield "Nelke" return yield "Lilie" for blume in blumen2_mitReturn(): print blume def blumen3_mitStopIteration(): yield "Tulpe" yield "Hyazinthe" ... 3935042698_v01.book Seite 23 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 24 Für diese Funktionen bzw. Syntaxkonstrukte verhalten sich die Generatoren also nicht anders als beliebige Sequenzen oder andere iterierbare Objekte. Das Interface, das eine Generatorinstanz dafür benutzt, ist das Iterator-Pro- tokoll von Python. Dieses ist Thema des folgenden Kapitels. 1.2.2 Das Iterator-Protokoll Während Generatoren immer in Python geschriebene Funktionen mit yield- Anweisungen sein müssen, können alle Typen per Iterator-Protokoll (http:// www.python.org/peps/pep-0234.html) für die einfache Iteration aufbereitet werden. Alle diese Objekte haben, damit sie als iterierbar anerkannt werden, die magische Funktion __iter__(). Diese Funktion wird aufgerufen, um einen Iterator für das Objekt zu bekommen. Ein Objekt, das diese Funktion hat, entspricht einer Java-Klasse, die Iterable implementiert. Auch die type::iterator-Iteratoren in den STL-Collection-Klassen von C++ sind nichts anderes. Die einzige Anforderung an das von __iter__() zurückgelie- raise StopIteration yield "Mohn" for blume in blumen3_mitStopIteration(): print blume def blumen4_mitValueError(): yield "Narzisse" yield "Dahlie" raise ValueError yield "Sonnenblume" try:blumenGen = blumen4() for blume in blumenGen: print blume except ValueError: try:blumenGen.next() except StopIteration: print "StopIteration-Ausnahme abgefangen" 3935042698_v01.book Seite 24 Freitag, 14. Oktober 2005 3:31 15
Einführung & Übersicht 25 ferte Objekt ist, dass es eine next()-Methode hat. Ob dieses Objekt eine Ge- neratorinstanz oder eine selbst geschriebene Klasse ist, spielt keine Rolle. In diesem Sinne ist auch der Begriff "Protokoll" in Python zu verstehen: Ein Ob- jekt unterstützt ein Protokoll dann, wenn es die Methoden des Protokolls im- plementiert (sog. duck typing). Wie die for-Schleife Sequenzen behandelt, zeigt das folgende Beispiel. Eine Erklärung der einzelnen Schritte: 1. Iterator für das übergebene Objekt holen. 2. Es wird überprüft, ob das zurückgelieferte Objekt wie ein Iterator "aus- sieht". 3. Die Schleifenvariable wird initialisiert. 4. In einer Endlosschleife wird der eigentliche Code der for-Schleife aus- geführt. 5. Der nächste Wert des Iterators wird geholt. 6. Die Schritte 4 und 5 werden so lange ausgeführt, bis eine StopIteration- Ausnahme auftritt. # for-intern.py try:it = [1, 2, 3].__iter__() getattr(it, "next") except AttributeError: raise TypeError, "iteration over non-sequence" try:i = it.next() while True: # Anfang Schleifencode print i # Ende i = it.next() except StopIteration: del it 3935042698_v01.book Seite 25 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 26 Klassen mit Unterstützung für Iteration Das nächste Beispiel zeigt, dass sich ein Generator und eine Klasse, die das Iterator-Protokoll implementiert, austauschbar verwenden lassen. Beide er- stellen die Folge der Fibonacci-Zahlen. In der while-Schleife unten werden beide Objekte identisch benutzt und es zeigt sich, dass zumindest die ersten 100 Elemente identisch sind. Das Iterator-Protokoll wurde in Version 2.1 zu Python hinzugefügt und wird mittlerweile von allen Containertypen und vielen anderen Klassen unter- stützt. In Python 2.1 musste man, um zeilenweise über eine Datei zu iterie- ren, noch das Modul xreadlines benutzen. Davor hatte man nur die Wahl, entweder die ganze Datei mit file.readlines() in den Speicher zu lesen, was bei großen Dateien zu Verzögerungen führte, oder man schrieb dafür # iterprotocol.py MAX = 100 def FiboGenerator(): x,y, =0,1 while True: yield y x,y=y,x+y class FiboIterProtocol(object): def __init__(self): self.x, self.y, self.z = 0, 1, 1 def __iter__(self): return self def next(self): self.z += self.x self.x, self.y = self.y, self.z return self.x f1 = FiboGenerator() f2 = FiboIterProtocol() for i in range(100): assert f1.next() == f2.next() 3935042698_v01.book Seite 26 Freitag, 14. Oktober 2005 3:31 15
Einführung & Übersicht 27 eine while-Schleife. Seit Python 2.2 kann man direkt über das file-Objekt iterieren, das die Zeilen der Datei einzeln zurückliefert. Die Funktion iter() Benötigt man den Iterator eines Objekts, kann man die eingebaute Funktion iter(x) benutzen. Dieser Aufruf ist äquivalent zu x.__iter__(), sieht aber ästhetischer aus. Manchmal hat man es mit einer Funktion zu tun, die sich wie ein Iterator verhält, aber kein Generator ist. Ein bekanntes Beispiel ist die Methode fetchone() eines Python-Datenbankcursors nach der DB-API-Spezifikation 2.0 (http://www.python.org/peps/pep-0249.html). Diese Funktion liefert bei jedem neuen Aufruf ein weiteres Recordset aus dem Ergebnis der letzten Abfrage zurück. Gibt es keine weiteren Datenreihen mehr, wird None zu- rückgegeben. Anstatt einer while-Schleife kann man jetzt iter(callable, sentinel) benutzen. Als callable übergibt man die Methode, als sentinel den Wert, der das Ende der Sequenz signalisiert. # xreadlines-fileiter.py import sys if sys.hexversion < 0x2020000: import xreadlines print "# Benutze xreadlines" it = xreadlines.xreadlines(file(sys.argv[1])) else:print "# Benutze file-Objekt" it = file(sys.argv[1]) for line in it: print line[:-1] cursor.execute("SELECT * FROM some_table") for row in iter(cursor.fetchone, None): ... 3935042698_v01.book Seite 27 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 28 Aber natürlich muss man sich nicht auf iteratorähnliche Funktionen be- schränken. Im zweiten Beispiel werden so lange Zufallszahlen generiert, bis eine 6 auftritt (die Ähnlichkeit zum Würfeln ist frappierend). Danach wird die Summe der vorher generierten Zufallszahlen berechnet. Bei komplexeren Iterationen muss man dann aber auf Generatoren zurück- greifen, beispielsweise wenn am Ende der Iteration eine Ausnahme gewor- fen wird oder explizit getestet werden muss, ob noch weitere Werte existie- ren. 1.2.3 Generatorvariationen Koroutinen in Python Natürlich sind Koroutinen viel zu interessant, als dass es keine Python- Implementation gäbe. Sie sind Teil von py.lib (http://codespeak.net/py), einer innovativen Sammlung von Python-Modulen aus dem Dunstkreis der PyPy-Entwickler (http://codespeak.net/pypy). Die Autoren sind Armin Rigo (Psyco) und Christian Tismer (Stackless). Die so genannten Greenlets unterstützen genau die Art von Programmfluss, wie er in der Erklärung von Koroutinen vorgestellt wurde. Das Beispiel in Kapitel 1.1.1 benutzt exakt dieses Modul. Mit Greenlets lassen sich auch die echten Generatoren von Python nachbauen, ohne dass eine yield-Anweisung erforderlich wird. Der Code dazu steht in Kapitel 1.6.3. Generatoren auf Eis Denkt man ein wenig über Generatoren und ihre vielen Anwendungsfälle nach, drängt sich natürlich die Idee eines "pickle-baren" Generators gerade- zu auf: Einige strategische yield-Anweisungen können ein lang laufendes Programm unterbrechen und seinen kompletten Zustand einfrieren. Ohne bisher erhaltene Ergebnisse zu zerstören, könnte man die Generatorinstanz # iter-sentinel.py from random import randint s = sum(iter(lambda: randint(1, 6), 6)) print "Summe aller Augen vor der ersten 6:", s 3935042698_v01.book Seite 28 Freitag, 14. Oktober 2005 3:31 15
Einführung & Übersicht 29 auf einem anderen System laden und weiter ausführen. Sieht man von prak- tischen Beschränkungen wie geöffneten Dateien und anderen Ressourcen, importierten Modulen und dem Zustand einer GUI großzügig ab, ignoriert man die Sicherheitsimplikationen und versucht, ein Generatorobjekt zu "pickeln", so wird man in seinen Erwartungen bitter enttäuscht: In den all- gemein verbreiteten Implementationen von Python (CPython, Jython) wird das "Pickeln" von Generatoren nicht unterstützt. Um dieses Verhalten aus- zuprobieren, muss man auf Stackless (http://www.stackless.com) zurück- greifen, eine von Christian Tismer geschriebene Variante von CPython, die ohne die Verwendung eines C-Stacks zusätzlich zu den Stackframe-Objek- ten des Interpreters auskommt. Die Entwicklung begann im Jahr 1999 und zeitweise war geplant, den C-Stack aus CPython selbst zu entfernen. Dies ist aber (leider?) nie geschehen. Über die Jahre ist Stackless mehrmals neu ge- schrieben und immer wieder verbessert worden. Die letzte Aktivität stammt von 2004 und sie ist wohl zugleich der endgültige Stand, denn der Entwick- ler hat mittlerweile zu PyPy gewechselt. Wie im Codebeispiel zu sehen, wird zunächst ein simpler Generator gestar- tet. Die erste for-Schleife liest zehn Werte aus. Dann wird die Generator- instanz mit üblichem Pickling-Code in eine Datei geschrieben und anschlie- ßend gelöscht. # stackless-pickling.py import os import pickle def SomeGenerator(): x=1 while True: yield "*" * x x+=1 gen = SomeGenerator() print "Original-Generator" for x in range(0, 10): print gen.next() print "Speichern & Laden" ... 3935042698_v01.book Seite 29 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 30 Sofort im Anschluss wird die Generatorinstanz wieder aus der Datei einge- lesen und weiter ausgeführt. Offensichtlich ist kein "Kälteschaden" entstan- den. Für dieses Beispiel wird der Stackless-Python-Interpreter benötigt. Quell- code und Binaries können von http://www.stackless.com heruntergeladen werden. Die Beschränkung auf pickelbare Generatoren wird den Fähigkeiten von Stackless nicht gerecht. Wer mehr über Stackless erfahren möchte, sollte sich auf der Webseite umsehen und die Artikel von Christian Tismer lesen. 1.3 Generatorausdrücke Schon seit geraumer Zeit gibt es in Python List-Comprehensions. Diese Ausdrücke stellen Abkürzungen für map() und filter() dar und sind von funktionalen Programmiersprachen wie Haskell inspiriert. In Python 2.4 wurden die Generatorausdrücke eingeführt. Der wichtigste Unterschied zu den List-Comprehensions ist, dass Generatorausdrücke nicht unmittelbar zu einer neuen Liste evaluiert werden, sondern einen Generator zurückliefern. Der Code im Generatorausdruck kommt immer dann zur Ausführung, wenn ein neues Element geholt wird. Die Ausführung erfolgt also verzögert ("lazy evaluation"), im Gegensatz zur List-Comprehension ("eager evaluation" oder "strict evaluation"). Der Vorteil dieses Ausführungsmodells kommt name = os.tmpnam() fridge = file(name, "w") pickle.dump(gen, fridge) fridge.close() del gen # stackless-pickling.py, Fortsetzung fridge = file(name) print "Wiederbelebter Generator" gen2 = pickle.load(fridge) for x in range(0, 10): print gen2.next() 3935042698_v01.book Seite 30 Freitag, 14. Oktober 2005 3:31 15
Generatorausdrücke 31 dann zum Tragen, wenn die zu erstellende Liste sehr umfangreich ist. Bei ei- ner List-Comprehension muss also der Speicher für eine Liste alloziert wer- den. Enthält der Ausdruck einen Filter, dann kann die Länge der Liste auch nicht vorher festgestellt werden, und man muss in Kauf nehmen, dass man möglicherweise zu viel Speicher alloziert, oder die Liste dynamisch aufbau- en. Die Speicherallokation entfällt bei den Generatorausdrücken, dafür hat man zusätzlichen Overhead beim Auslesen jedes Elements. Aber wenn nicht mehrere Elemente gleichzeitig gebraucht werden (zum Beispiel für das Sortieren der Liste), ist ein Generatorausdruck zu empfehlen. Syntaktisch ist der Unterschied zwischen Generatorausdrücken und List- Comprehensions marginal. Anstatt der umschließenden eckigen Klammern bei List-Comprehensions werden runde Klammern verwendet. Die Genera- torausdrücke können dafür auch andere Klammern "wiederverwenden". Ist das einzige Argument einer Funktion ein Generatorausdruck, so kann man sich die doppelten Klammern für Funktionsaufruf und Klammerung des Ausdrucks sparen und sie durch einfache Klammern ersetzen, wie man im folgenden Codebeispiel sieht. Wie bereits erwähnt, erfolgt die Auswertung bei einem Generatorausdruck verzögert. Haben die im Ausdruck aufgerufenen Funktionen Seiteneffekte, ist die geänderte Ausführungsreihenfolge zu beachten, gerade wenn man be- stehende List-Comprehensions durch Generatorausdrücke ersetzt. Im fol- genden Codebeispiel werden bei der List-Comprehension zuerst alle Log- Ausgaben gemacht, dann folgt die Zeichenkette. Beim Generatorausdruck wechseln sich Log- und Zeichenkette-Ausgabe dagegen ab. # genexpr-vs-listc.py a=sum([x*xforxinxrange(1,100)ifx%2==0]) b=sum(x*xforxinxrange(1,100)ifx%2==0) printa==b def makeBouquet(flower): s = "%snstrauß" % (flower, ) print "** LOG: Erstelle %s" % (s,) return s ... 3935042698_v01.book Seite 31 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 32 Ein weiterer Unterschied zu List-Comprehension ist, dass die Schleifen- variable nicht zu den lokalen Variablen hinzugefügt wird und nach der Be- rechnung zur Verfügung steht. Hat man also Code, der nach einer List-Com- prehension auf die Schleifenvariable zugreift (eine Technik, die meiner Meinung nach mindestens fragwürdig ist), ist Vorsicht geboten, da beim Ge- neratorausdruck ein NameError ausgelöst wird. Der Grund für dieses Verhalten ist, dass ein Generatorausdruck seinen eige- nen Gültigkeitsbereich für Variablennamen besitzt. Die Schleifenvariable kann sogar eine gleichnamige Variable überdecken ("variable shadowing"), ohne dass deren Wert geändert wird. flowers = ("Hortensie", "Rose", "Nelke", "Frisie") def printOut(it): for bouquet in it: print bouquet print "== Mit einer List-Comprehension ==" printOut([makeBouquet(f) for f in flowers]) print "== Mit einer Generatorausdruck ==" printOut(makeBouquet(f) for f in flowers) # genexpr-vs-listc.py, Fortsetzung def listc(): [u*u for u in range(0, 10)] assert u == 9 def genexpr(): list(u*u for u in range(0, 10)) try: assert u == 9 except NameError: print "NameError abgefangen!" listc() genexpr() 3935042698_v01.book Seite 32 Freitag, 14. Oktober 2005 3:31 15
Generatorausdrücke 33 Wenn beide Spezialfälle nicht zutreffen, kann prinzipiell jede List-Compre- hension in Argumentlisten durch einen Generatorausdruck ersetzen, voraus- gesetzt, diese wird nicht als Liste, sondern per Iterator-Protokoll benutzt. Wenn die List-Comprehension Argument für eine for-Schleife ist, kann man sie daher bedenkenlos umwandeln. Für Python 3000 (http://www.python.org/peps/pep-3000.html) ist auch ge- plant, List-Comprehensions intern als Generatorausdrücke zu behandeln, die sofort evaluiert werden. [f(x) for x in y] würde dann vom Bytecode- Compiler zuerst in list(f(x) for x in y) umgeschrieben und entsprechend übersetzt werden. Es ist auch geplant, map() und filter() komplett aus Py- thon zu entfernen. Die Anordnung der Befehle in einer List-Comprehension ist natürlicher, so die Argumentation, zumal auch Lambda-Funktionen nach den Wünschen der Autoren von PEP 3000 komplett wegfallen sollen. Benchmarking Natürlich stellt sich die Frage, wie hoch der Geschwindigkeitsgewinn durch die eingesparte Speicherallokation wirklich ist. Das folgende Codebeispiel zeigt die Geschwindigkeitsunterschiede zwischen List-Comprehensions und Generatorausdrücken, für den Fall, dass sehr große Listen verarbeitet werden. Dazu wird zuerst eine Liste mit 500.000 Integer-Elementen erstellt. Diese Zahlen dienen zur Berechnung von Pi nach Leibniz (http://de.wiki- pedia.org/wiki/Kreiszahlberechnung_nach_Leibniz). Der Algorithmus ist als List-Comprehension, Generatorausdruck und for-Schleife geschrieben. # leibniz-pi.py niters = 10**6 n = range(1, niters, 4) + range(-3, -niters, -4) ... sum([1/xfor xinn]) ... sum(1/xforxinn) ... value = 0 ... 3935042698_v01.book Seite 33 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 34 Die gemessenen Zeiten für die Ausführung der Codezeilen auf einem Think- Pad T41p (Pentium M 1700, 512 MB RAM, Python 2.4.1) sind folgende: Allerdings sind die Zahlen, wie bei allen Benchmarks, mit Vorsicht zu ge- nießen und sollten nicht für ideologische Grabenkämpfe herangezogen wer- den. Generatorausdrücke sind in diesem Beispiel schneller, allerdings liegt das Hauptaugenmerk auf dem Speicherverbrauch, der mit diesem Experi- ment nicht gemessen wird. Um sich selbst einen Eindruck zu verschaffen, ist es am besten, bestehenden Code aus eigenen Projekten selbst zu bench- marken. 1.4 Das itertools-Modul Zusammen mit der "richtigen" Einführung von Generatoren in Python 2.3 (in Python 2.2 waren sie verfügbar, wenn sie explizit aus __future__ ange- fordert wurden) kam das Modul itertools in die Standardbibliothek. Es enthält Funktionen, die alle mehr oder weniger mit Generatoren und "lazy evaluation" zu tun haben. So gibt es die Generatorvarianten von zip(), fil- ter(), slice() und map(). Diese Funktionen haben alle ein i im Namen (izip() usw.), um sie von den normalen Varianten zu unterscheiden. Aber anstatt die Argumente sofort auszuwerten und eine neue Liste zurückzulie- fern, wird ein Generator erstellt, aus dem alle Werte einzeln geholt werden können. Die Idee hinter diesen Funktionen ist, dass sie alle als kleine Bau- steine, die jeweils eine beschränkte, aber klare Aufgabe haben, zu komple- xen Ausdrücken kombiniert werden können. forxinn: value+=1/x ... Methode Zeit in s List-Comprehension 0,33 Generatorausdruck 0,23 Schleife 0,32 Tabelle 1.1: Benchmarking-Werte für Listenverarbeitung 3935042698_v01.book Seite 34 Freitag, 14. Oktober 2005 3:31 15
Das itertools-Modul 35 Zu einigen dieser Bausteine wollen wir uns hier einfache Anwendungsbei- spiele ansehen. Im ersten Codefragment werden zwei Funktionen gezeigt: chain(*iterables) kann mit einer beliebig großen Anzahl von iterierbaren Objekten aufgerufen werden und liefert einen Iterator, der alle diese Iterato- ren miteinander verkettet. Das strikte Pendant dazu ist die Addition beliebig vieler Listen oder list.extend(). Mit tee(iterable, n) kann man von einem Iterator beliebig viele, vonein- ander scheinbar unabhängige Kopien erstellen. Wie der Beispielcode zeigt, wird der Original-Iterator (in diesem Fall ein Generator) nicht sofort ausge- lesen. Erst wenn von einer der Kopien ein Wert angefordert wird, werden Werte vom Original gelesen. Der Wert wird in einer internen Liste zwi- schengespeichert, damit die anderen Kopien die gleichen Werte zurückge- ben können. Der Generator wird im Codebeispiel nur einmal ausgeführt, aber kopie1 und kopie2 erhalten beide seine Werte. Das strikte Äquivalent zu diesem Code ist die Kopie einer Liste mit list[:]. Im zweiten Beispiel greifen wir auf die Fibonacci-Generatoren aus Kapitel 1.1.2 zurück. Diemal benutzen wir die Funktion takewhile(predicate, iterable), um aus beiden die Fibonacci-Zahlen unter 100 auszulesen und die beiden Sequenzen zu vergleichen. Diese Funktion liefert so lange Werte zurück, wie predicate(x) für ein x aus iterable wahr ist. Wir benutzen hier eine anynome Funktion, die überprüft, ob die neue Zahl kleiner als 100 ist. # tee.py import itertools def Original(): print "k: 1. Aufruf" yield "La" print "k: 2. Aufruf" yield "Le" print "k: 3. Aufruf" yield "Lu" kopie1, kopie2 = itertools.tee(Original(), 2) for x in itertools.chain(kopie1, kopie2): print "Ergebnis:", x 3935042698_v01.book Seite 35 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 36 Die letzte Funktion, die wir uns ansehen, ist groupby(sequence, keyfunc). Diese Funktion ist nicht trivial zu handhaben, denn die Argumente müssen bestimmten Voraussetzungen entsprechen. Für jedes Element x im Argu- ment sequence wird keyfunc(x) aufgerufen, um einen Schlüssel für jedes Element zu erhalten. Elemente mit gleichen Schlüsseln kommen in eine ge- meinsame Liste. Der Rückgabewert von groupby() ist ein Iterator, der für alle aufgetretenen Schlüssel das Tupel aus Schlüssel und assoziierten Wer- ten zurückliefert. Allerdings dürfen die Elemente in der Eingabesequenz nicht in beliebiger Reihenfolge stehen, denn alle Elemente mit gleichen Schlüsselwerten müssen aufeinander folgen. Dies kann man in Python 2.4 mit sorted(sequence, key=keyfunc) erreichen. Im Beispielcode liefert partition(m, sd) die neue Funktion für die Schlüs- sel. Sie überprüft für einen Wert, ob er mehr als zwei Standardabweichungen (sd) vom Mittelwert (m) nach unten oder nach oben abweicht. Die Schlüssel sind dann -1, 1 oder 0, wenn der Wert innerhalb dieses Bereichs liegt. # iterprotocol.py, Fortsetzung from itertools import takewhile cutoff = lambda x: x < 100 u = list(takewhile(cutoff, FiboGenerator())) v = list(takewhile(cutoff, FiboIterProtocol())) printu==v # groupby-example.py from random import normalvariate def partition(m, sd): def _inner(x): ifx<m-2*sd: return -1 elifx>m+2*sd: return 1 else: return 0 return _inner sd=5,10 3935042698_v01.book Seite 36 Freitag, 14. Oktober 2005 3:31 15
Anwendungsbeispiele 37 Zuerst werden 1000 Zufallswerte auf einer Normalverteilung mit Mittelwert 5 und Standardabweichung 10 erstellt und mit partition(5, 10) sortiert. Danach wird die sortierte Liste mit groupby aufgetrennt und die Anzahl der Elemente in den Listen wird berechnet. Das Modul enthält noch einige weitere interessante Funktionen, die man sich ansehen sollte. Oft kann man durch ihre Benutzung einige Zeilen Code sparen. In Bezug auf eine große Anzahl von Funktionen verschiedenster Art wurde und wird diskutiert, ob sie zu itertools hinzugefügt werden sollen oder nicht. Allerdings sind die Maintainer vorsichtig beim Aufnehmen neuer Funktionen, da itertools nicht zu einer Sammelstelle von Code werden soll, der nicht in die __builtins__ gekommen ist und unter Umständen nur wenig Sinn für Iteratoren macht. Einige der Funktionen, die in der Vergangenheit für itertools vorgeschlagen wurden, finden sich in goopy (http://goog- goopy.sourceforge.net/), einer recht losen Sammlung nützlicher Funktionen für funktionales Programmieren in Python. 1.5 Anwendungsbeispiele Bisher hatten die Beispiele für Generatoren meist Lehrcharakter, denn nie- mand wird für das Iterieren über Blumennamen einen Generator schreiben müssen. Viele Beispiele waren so klein, dass sie sich oft noch mit einem Ge- # groupby-example.py, Fortsetzung keyfunc = partition(m, sd) w = sorted( (normalvariate(m, sd) for x in range(1000)), key = keyfunc) for k, g in itertools.groupby(w, keyfunc): l = len(list(g)) ifk== -1: print "Anzahl untere Ausreißer:", l elifk==1: print "Anzahl obere Ausreißer:", l 3935042698_v01.book Seite 37 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 38 neratorausdruck schreiben ließen. Was aber bringt es wirklich, dass ein Ge- nerator eine Closure ist und der Ausführungszustand zwischen zwei Aufru- fen erhalten bleibt, wenn man doch auch eine Klassemethode schreiben kann, bei der sich der Zustand der letzten Ausführung in den Mitgliedsvari- ablen der Klasse speichern lässt? 1.5.1 Generatorklassen vs. Generatoren Natürlich kann man viele Generatoren in Klassen umschreiben, die das Ite- rator-Protokoll implementieren. Diese Klassen können dann, wie bereits ge- sehen, wie ein Generator benutzt werden. Der Vorteil der Generatoren gegenüber der selbst geschriebenen Klasse ist hier aber in der Tipp-Ökonomie zu suchen, denn man erspart sich viel an in- terner Zustandsverwaltung, die sonst notwendig wäre. Außerdem ist der beste Code immer noch der Code, den man bei gleicher Funktionalität ein- gespart hat und daher nicht mehr pflegen muss. Das folgende Beispiel zeigt zwei Implementationen eines Algorithmus, der alle Untermengen einer Menge, die so genannte Obermenge, generiert. Bei der Generatorklasse kann man sehen, dass allein für die Implementation des Iterator-Protokolls einige Zeilen mehr Code notwendig sind. Außerdem braucht man ein zusätzliches Statusflag, um zu signalisieren, dass keine weiteren Untermengen mehr vorhanden sind. Der Generator benötigt diese Zustandsverwaltung nicht, denn nach der letz- ten Untermenge, die immer die leere Menge ist, wird eine StopIteration- Ausnahme ausgelöst und die Iteration ist beendet. Beide Implementationen sind rekursiv, aber der Generator ist deutlich kompakter. Außerdem können Programmierer, die das self in den Klassenmethoden von Python nicht mö- gen, zumindest hier aufatmen. 3935042698_v01.book Seite 38 Freitag, 14. Oktober 2005 3:31 15
Anwendungsbeispiele 39 # 22 Zeilen Code class SubsetGeneratorClass(object): def __init__(self, l): self._s = list(l) if len(l) > 0: self.sub = SubsetGeneratorClass(self._s[1:]) self._end = False self._getNext = True def __iter__(self): return self def next(self): if self._end: raise StopIteration if len(self._s) == 0: self._end = True return set() if self._getNext: self._x = self.sub.next() self._getNext = not self._getNext return set(self._s[0]) | self._x else: self._getNext = not self._getNext return self._x # 8 Zeilen Code def SubsetGenerator(l): l = list(l) if len(l) == 0: yield set() else:for sl in SubsetGenerator(l[1:]): yield set(l[0]) | sl yield sl ... 3935042698_v01.book Seite 39 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 40 Selbst in diesem einfachen Beispiel spart der Generator im Vergleich zur Klasse ungefähr 70% des Codes ein. Übrigens sind beide Implementationen nicht die kürzestmöglichen in Bezug auf die Anzahl der Codezeilen. Ich habe mich aber für diese Beispiele ent- schieden, weil sie aus meiner Sicht das Optimum zwischen Kürze und Ver- ständlichkeit darstellen. 1.5.2 Iteratoren nachrüsten Oft muss man Containerklassen benutzen, denen aufgrund ihres Alters oder wegen Designfehlern eine Implementation des Iterator-Protokolls fehlt. Al- lerdings sind andere Methoden zur Iteration vorhanden. Will man die Vor- teile des Iterator-Protokolls nicht aufgeben, bieten sich mehrere Lösungen an. Man kann eine Klasse ableiten, die das Iterator-Protokoll unterstützt, oder die Implementation der Klasse selbst ändern, so weit dies möglich ist. Wenn beides keine Optionen sind, kann man die __iter__()-Methode belie- biger Klassen zur Laufzeit zuweisen bzw. überschreiben. Das ist möglich, da anders als bei C++ die Methoden einer Klasse nicht fest in einer beim Kompilieren erstellten vtable stehen, sondern im Dictionary eines Typs bzw. einer Instanz (bereitgestellt im Attribut __dict__). Mitglieder einer Klasse können also beliebig eingefügt, gelöscht und modifiziert werden, eine Tech- nik, die gerade in der GUI-Programmierung sehr praktisch ist und oftmals das Erstellen einer Vererbungshierarchie erspart. Zum Beispiel kann man so das Listbox-Widget aus der GUI-Bibliothek PyQt (http://www.riverbank- def checkSet(theset, powerset): subsets = set() for subset in powerset: subsets.add(frozenset(subset)) assert theset.issuperset(subset) assert 2 ** len(theset) == len(subsets) theset = set("ABCD") checkSet(theset, SubsetGenerator(theset)) checkSet(theset, SubsetGeneratorClass(theset)) 3935042698_v01.book Seite 40 Freitag, 14. Oktober 2005 3:31 15
Anwendungsbeispiele 41 computing.co.uk/pyqt) nachträglich mit Unterstützung für Iteration aus- statten. Dazu schreibt man zunächst einen Generator, der für die eigentliche Itera- tion zuständig ist. Diesen Generator weist man dann der __iter__()-Metho- de der Klasse zu, die man erweitern möchte. Danach kann man Instanzen der Klasse auf die bisher gewohnte Art benutzen. 1.5.3 Microthreads Natürlich gibt es noch eine ganze Menge anderer Anwendungsfelder und viele davon sind nicht einfach nur Iteration über eine Datenfolge, auch wenn dies das Hauptanwendungsfeld darstellt. Interessant sind vor allem die Artikel von D. Mertz zu Microthreads (http://www-128.ibm.com/developer- works/linux/library/l-pythrd.html) und Automaten bzw. state machines (http://www-128.ibm.com/developerworks/library/l-pygen.html). Gerade in GUI-Anwendungen können Generatoren bei richtiger Anwendung Threads ersetzen. Wenn zum Beispiel mehrere Dateien gleichzeitig über FTP oder HTTP heruntergeladen werden sollen, kann man einen Generator schreiben, der jeweils eine Datei mit der urllib2 aus der Standardbibliothek in kleinen Stücken herunterlädt. Wenn die Datei komplett heruntergeladen ist, endet der Generator. Natürlich könnte man, anders als hier im Beispiel, die gele- senen Daten per yield zurückgeben. Allerdings besteht die Hauptaufgabe von yield hier ja gerade nicht darin, Daten zurückzugeben, sondern die Funktion zu unterbrechen. # listboxiterator.py import sys import qt def ListBoxIterator(listbox): i = listbox.firstItem() while i: yield i i = i.next() qt.QListBox.__iter__ = ListBoxIterator 3935042698_v01.book Seite 41 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 42 Um mehrere Dateien gleichzeitig zu lesen, werden alle Generatoren erstellt und in einer Liste gespeichert. Eine doppelte Schleife ruft nun alle Genera- toren so lange auf, bis alle beendet sind. In einem CLI-Programm ist das we- nig spektakulär, aber in einem Programm mit grafischer Benutzeroberfläche kann man dafür Sorge tragen, dass Ereignisse verarbeitet werden und die GUI nicht blockiert ist. # microthreads.py import urllib2 import logging CHUNKSIZE=1024 logging.getLogger().setLevel(logging.DEBUG) def download(url, dest): stream = urllib2.urlopen(url) logging.info("%s opened", url) yield None while True: data = stream.read(CHUNKSIZE) if data == '': logging.info("%s finished", url) return dest.write(data) yield None # microthreads.py, Fortsetzung urls=[ "http://www.heise.de/index.html", "http://www.golem.de/index.htm", "http://www.spiegel.de/index.html" ] class DummyWriter(object): def write(*args): pass loaders = [download(url, DummyWriter()) for url in urls] ... 3935042698_v01.book Seite 42 Freitag, 14. Oktober 2005 3:31 15
Erweiterungen 43 Wenn man sehr viele Microthreads laufen hat, kann man für die Ereignisver- arbeitung der GUI eigene Microthreads schreiben, die sich dann beliebig oft in die Liste der auszuführenden Microthreads einfügen lassen, damit das Programm auch bei einer großen Anzahl von Generatoren noch auf Benut- zereingaben reagieren kann. Zu beachten ist allerdings, dass hier ein koope- ratives Multitasking-Modell benutzt wird. Wenn ein Generator die Kontrolle nicht zurückgibt, ist das Programm blockiert. Daher muss man die Gene- ratoren mit Vorsicht programmieren und sicherstellen, dass sie für einen Aufruf immer nur eine kurze Zeit benötigen, bis sie die Kontrolle wieder zu- rückgeben. 1.6 Erweiterungen In Kapitel 1.1 haben wir die Beschränkungen der Generatoren gesehen. Da- her ist es an der Zeit, sich darüber Gedanken zu machen, wie man diese um- gehen kann. 1.6.1 Generatorattribute Möchte man das Verhalten eines Generators zur Laufzeit beeinflussen oder Konsumenten schreiben, muss man etwas um die aktuelle Generatorimple- mentation herumarbeiten, wenn man nicht auf zukünftige Erweiterungen warten oder komplett andere Implementationen wie die Greenlets benutzen möchte. Kommt es lediglich darauf an, einen Parameter zur Laufzeit zu ver- ändern, geht das recht komfortabel über globale Variablen. Allerdings ge- nießen globale Variablen zu Recht kein hohes Ansehen. Außerdem kann es zu unerwünschten Nebeneffekten kommen, wenn man mehr als eine In- stanz des Generators laufen hat, der durch die globale Variable beeinflusst wird. Daher wollen wir hier eine andere, komplexere Lösung betrachten. Ein ähnlicher Vorschlag wurde von Raymond Hettinger im Rezept 164044 while loaders: for i, loader in enumerate(loaders): try:loader.next() except StopIteration: del loaders[i] 3935042698_v01.book Seite 43 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 44 auf http://aspn.activestate.com gemacht. Der hier vorgestellte Ansatz ist einfacher, bietet aber nahezu die gleichen Möglichkeiten. Für den Datenaustausch wird in diesem Beispiel ein Objekt benutzt, das dem Generator beim Start übergeben wird, ähnlich dem self in Klassen- methoden. Der Dekorator @GenAttrs ist dafür zuständig, dass dem Generator dieses Objekt übergeben wird. Das folgende Beispiel zeigt eine Suchfunk- tion für Zeichenketten, bei der sich während der Ausführung die Suchrich- tung wechseln lässt. Als Argumente bekommt @GenAttrs die Attribute und ihre Startwerte, die in self enthalten sein sollen. In diesem Beispiel sind es searchDirection und wrapAround. Durch Verändern des ersten Attributs kann man zwischen zwei next()-Aufrufen die Suchrichtung ändern. # genattrs.py, Fortsetzung FORWARD = True BACKWARD = False @GenAttrs(searchDirection = FORWARD, wrapAround = True) def search(self, text, searchFor, start = -1): while True: if self.searchDirection == FORWARD: start = text.find(searchFor, start + 1) else: start = text.rfind(searchFor, 0, start) if start == -1: if not self.wrapAround: return else: continue yield markUp(text, start, len(searchFor)) # genattrs.py, Fortsetzung text = "Eine Rose ist eine Rose ist eine Rose." sIter = search(text, "Rose") print sIter.next() print sIter.next() sIter.searchDirection = BACKWARD ... 3935042698_v01.book Seite 44 Freitag, 14. Oktober 2005 3:31 15
Erweiterungen 45 Der Code produziert folgende Ausgabe: Die Implementation von @GenAttr sieht auf den ersten Blick kompliziert aus, vor allem weil mehrere Closures verwendet werden. Der äußere Deko- rator bekommt in **attrs die gewünschten Attribute. Der innere Dekorator übernimmt den eigentlichen Generator func und verpackt ihn in die Klasse _DataProxy. Wenn eine Instanz der Klasse erstellt wird, erhält diese die Wer- te aus **attrs. Sie werden beim Aufruf an den Generator übergeben, aber auch zur Steuerung der Generatorinstanz benutzt. print sIter.next() print sIter.next() sIter.searchDirection = FORWARD print sIter.next() print sIter.next() Eine @Rose@ ist eine Rose ist eine Rose. Eine Rose ist eine @Rose@ ist eine Rose. Eine @Rose@ ist eine Rose ist eine Rose. Eine Rose ist eine Rose ist eine @Rose@. Eine @Rose@ ist eine Rose ist eine Rose. Eine Rose ist eine @Rose@ ist eine Rose. # genattrs.py def GenAttrs(**attrs): def _inner(func): class _DataProxy(object): def __init__(self, *args, **kwargs): if func.func_code.co_argcount < 1: raise TypeError, "%s has to take at least one argument" % (func.__name__,) self._f = func(self, *args, **kwargs) self.__dict__.update(attrs) def __iter__(self): return self._f ... 3935042698_v01.book Seite 45 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 46 Zu beachten ist hierbei, dass die so erweiterten Generatoren ähnlich den Klassenmethoden immer ein zusätzliches Argument am Anfang der Argu- mentliste haben müssen, in der die _DataProxy-Instanz überreicht wird. 1.6.2 Konsumenten Kontinuierlicher Datenaustausch über ein Attribut ist kompliziert, da vor je- dem Aufruf das Attribut, das die Daten übernimmt, neu gesetzt werden muss. Wir können den Dekorator aus dem vorherigen Beispiel weiterver- wenden und ihn etwas erweitern, damit man bei next()-Aufrufen Argu- mente an den Generator übergeben kann. Die einzige Veränderung betrifft next(), das jetzt ein Argument hat. Dieses Argument wird als Attribut data in _DataProxy gespeichert, das der Generator dann lediglich auslesen muss. Im folgenden Anwendungsbeispiel wird ein so erweiterter Generator be- nutzt, um Kommentare aus einer Python-Quelldatei zu entfernen. Sobald in CommentStripper eine Raute gelesen wird, werden bis zum Zeilenende alle anderen Zeichen ignoriert. In einer neuen Zeile geht es dann wieder normal weiter. def next(self): return self._f.next() return _DataProxy return _inner # next-w-args.py ... def next(self, data = None): self.data = data return self._f.next() ... def FileReader(filename): for line in file(filename): for c in line: yield c ... 3935042698_v01.book Seite 46 Freitag, 14. Oktober 2005 3:31 15
Erweiterungen 47 Die erweiterten Generatoren lohnen sich aber nicht nur dann, wenn zwi- schen den verschiedenen Aufrufen ein Zustand zwischengespeichert wer- den muss (in diesem Beispiel, ob sich die nächsten Zeichen in einem Kom- mentar befinden oder nicht). Immerhin erspart man sich im Vergleich zu einer Klasse, die man ja auch schreiben könnte, einige Zeilen Code und be- kommt alle Möglichkeiten der Generatoren umsonst dazu, falls man sie ir- gendwann einmal brauchen sollte. 1.6.3 Generatoren mit Greenlets Wie schon in Kapitel 1.2.3 angedeutet, kann man mit Greenlets Python-Ge- neratoren simulieren, ohne dass man eine yield-Anweisung benötigt. Die py.lib-Distribution enthält dafür sogar eine einfache Beispielimplementa- tion, die wir uns hier ansehen werden. Das erste Beispiel zeigt, wie man mit ihr einen Generator schreibt. @DataTransfer() def CommentStripper(self): while True: if self.data == "#": while True: yield "" if self.data == "\n": yield self.data break else:yield self.data s = CommentStripper() for char in FileReader("test.py"): sys.stdout.write(s.next(char)) @Generator def blumen(): Yield("Rose") Yield("Geranie") print list(blumen()) 3935042698_v01.book Seite 47 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 48 Die yield-Anweisungen werden hier einfach durch einen Funktionsaufruf ersetzt. Da der Generator jetzt eine ganz normale Funktion ist, wird er vom Bytecode-Compiler auch nicht speziell behandelt. Deshalb muss man den Dekorator @Generator benutzen, der die Funktion in der Klasse verpackt, die die Generatorfunktionalität bereitstellt. Die folgenden Codebeispiele, die die Interna von Yield(value) und der Klasse genlet zeigen, sind der Datei py/c-extension/greenlet/test_generator.py aus py.lib entnommen. Natürlich implementiert genlet das Iterator-Protokoll, sonst hätte man den Generator im obigen Beispiel nicht mit list() benutzen können. In der Me- thode next() und in der freien Funktion Yield(value) sehen wir die schon bekannte Methode greenlet.switch(value), die für den Kontextwechsel zwischen zwei Greenlets zuständig ist. In next() wird zunächst der aktuelle Kontext im Attribut parent gespeichert, um wieder zu dieser Funktion zu- rückspringen zu können. Dann wird per self.switch() die eigentliche Funktion (bzw. der Pseudo-Generator) aktiviert. # greenyield.py from py.magic import greenlet class genlet(greenlet): def __init__(self, *args, **kwds): self.args = args self.kwds = kwds def run(self): fn, = self.fn fn(*self.args, **self.kwds) def __iter__(self): return self def next(self): self.parent = greenlet.getcurrent() result = self.switch() if self: return result else: raise StopIteration ... 3935042698_v01.book Seite 48 Freitag, 14. Oktober 2005 3:31 15
Erweiterungen 49 Rekursive Generatoren Oft hat man einen Generator mit rekursivem Aufruf. In diesem Fall muss man immer eine for-Schleife schreiben, die alle Werte aus dem tieferen Ge- nerator explizit mit yield zurückgibt. Nehmen wir als Beispiel einen Gene- rator, der alle Blätter eines binären Baums liefert. In leaves werden zwei for-Schleifen benötigt, um alle Blätter aus dem linken und dem rechten Ast zurückzugeben. In leaves2 erledigt diese Aufgabe die erdachte Anweisung yieldall. Natürlich kann man in beiden Beispielen die Generatoren für die beiden Äste mit itertools.chain() zusammenfügen. def Generator(func): class Genlet(genlet): fn = (func,) return Genlet def Yield(value): g = greenlet.getcurrent() if not isinstance(g, genlet): raise RuntimeError, 'yield outside a genlet' g.parent.switch(value) def leaves(tree): if isPair(tree): for i in leaves(left(tree)): yield i for i in leaves(right(tree)): yield i else:yield tree def leaves2(tree): if isPair(tree): yieldall leaves2(left(tree)) yieldall leaves2(right(tree)) else:yield tree 3935042698_v01.book Seite 49 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 50 Das Verhalten des Schlüsselworts yieldall kann man für die genlet-Klasse mit Greenlets erstellen. Die for-Schleife ist hier auch zu sehen, aber alle Werte aus dem Generator gen werden an das übergeordnete Greenlet zurück- gegeben, wie in Yield(value) auch. Mit dieser Funktion kann man rekursive Generatoren ohne for-Schleifen schreiben und man spart sich ein paar Zeilen Code. Ob das allein wirklich zum Umstieg von Python-Generatoren auf Greenlets motivieren kann, ist fraglich. Allerdings zeigt schon dieses sehr einfache Beispiel, wie man mit Greenlets die Flusskontrolle in einem Python-Pro- gramm auf interessante Art und Weise beeinflussen kann. Sie ermöglichen einen Programmierstil, der mit Python sonst so nicht möglich ist. Wenn man also in einem eigenen Projekt sowieso schon die Greenlets benutzt, dann sollte man auch an die hier vorgestellten Einsatzmöglichkeiten denken. Ei- nige Beschränkungen muss man aber akzeptieren. Zum Beispiel kann ein normaler Python-Debugger Kontextwechsel der Greenlets nicht verfolgen. Ein kurzer Test mit Eric3 zeigt, dass Unterbrechungspunkte in den Pseudo- Generatoren funktionieren, man aber nicht per Einzelschrittanweisungen in den Code eines genlet-Generators gelangen kann. # greenyield.py, Fortsetzung def YieldAll(gen): g = greenlet.getcurrent() if not isinstance(greenlet.getcurrent(), genlet): raise RuntimeError, 'yield outside a genlet' for v in gen: g.parent.switch(v) # greenyield.py, Fortsetzung @Generator def leaves2(tree): if isPair(tree): YieldAll(leaves2(left(tree))) YieldAll(leaves2(right(tree))) else:Yield(tree) 3935042698_v01.book Seite 50 Freitag, 14. Oktober 2005 3:31 15
Die Zukunft 51 Im nächsten Abschnitt werden wir sehen, dass einige der hier vorgestellten Erweiterungen so oft gefordert wurden, dass sie demnächst Eingang in den Sprachkern finden werden. 1.7 Die Zukunft Die Entwicklung der Generatoren bleibt nicht stehen und Möglichkeiten zur Verbesserung gibt es viele. Die beiden PEPs (Python Enhancement Pro- posals) 342 und 343 enthalten einige sinnvolle Erweiterungen, die wir mit- tels hypothetischem Beispielcode etwas näher betrachten wollen. 1.7.1 PEP 342: "Coroutines via Enhanced Iterators" PEP 342 enthält eine Reihe von Erweiterungen für Generatoren, damit man diese mehr wie Koroutinen verwenden kann. Die wichtigste Änderung ist, dass es möglich sein wird, mit einer yield-Anweisung auch Daten an einen laufenden Generator zu übergeben. Was in Kapitel 1.6.2 über das zusätzlich übergebenene Objekt realisiert wurde, wird hier damit erreicht, dass yield nun ein Ausdruck ist und auch auf der rechten Seite einer Zuweisung stehen kann. Die Anweisung funktioniert jetzt also fast wie switch() bei den Greenlets. Intuitiv wird erkennbar, dass zunächst ein normales yield ausgeführt wird. Um bei einem erneuten Aufruf Daten an den Generator zu übergeben, muss man anstatt der bekannten next()-Methode die neue Methode send(x) des Generators benutzen. Das Argument dieses Funktionsaufrufs ist dann der Rückgabewert des yield-Ausdrucks. Wenn man kein Argument angibt, wird None geliefert. Die beiden Methoden sind in ihrem Verhalten sonst identisch, sie geben den nächsten Wert des Generators zurück oder führen zu einer StopIteration-Ausnahme. Im Kontext von PEP 342 sind jetzt auch argumentlose yield-Ausdrücke er- laubt, die einfach als yield None interpretiert werden. Ein Rückgabewert ist #schnipp x=yield(1,2,3) #schnapp 3935042698_v01.book Seite 51 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 52 dann nicht mehr zwingend erforderlich, was die Abkehr vom Python-Gene- rator als reinem Datenerzeuger syntaktisch noch mehr widerspiegelt. Im Microthread-Beispiel musste man yield immer noch ein Dummy-Argument mitgeben, was mit dieser Ergänzung nicht mehr notwendig ist. Weitere neue Methoden in PEP 342 g.throw(type, value, traceback) führt dazu, dass dort, wo die Ausführung des Generators beim letzten yield gestoppt wurde, die Ausnahme type er- zeugt wird. Wird diese nicht innerhalb des Generators abgefangen, passiert das Gleiche, wie wenn die Ausnahme ursprünglich im Generator aufgetre- ten wäre, denn sie wird an den aufrufenden Code zurückgegeben. Fängt der Generator die Ausnahme aber selbst ab, wird die Ausführung normal fort- gesetzt und g.throw() gibt den Wert des nächsten yield zurück. g.close() ist eine Abkürzung von g.throw(GeneratorExit, Generator- Exit(), None). Die neue eingebaute Ausnahme GeneratorExit muss zur Beendigung eines Generators führen. Wenn der Generator diese Ausnahme also nicht abfängt, verhält er sich korrekt. Natürlich kann alternativ auch eine StopIteration ausgelöst werden, denn beide Ausnahmen werden von g.close() abgefangen und als erfolgreiche Beendigung der Generators an- gesehen. g.__del__() ist ein Alias dieser Methode und wird vom Garbage Collector aufgerufen, wenn alle Referenzen auf den Generator ungültig werden. Das Ziel ist, dass im Generator enthaltene finally-Blöcke ausgeführt werden können. Bisher konnten yield-Anweisungen nicht in einer try-finally-Anweisung stehen. Der Grund ist, dass ein suspendierter Generator bisher zu einem be- liebigen Zeitpunkt gelöscht werden kann, ohne dass der Generator über sei- ne eigene Entsorgung befragt wird. Allerdings ist deren Ausführung garan- tiert, daher war dieses Konstrukt bisher nicht erlaubt und führte zu einem Syntaxfehler. Im Falle der Ausnahme, die im Generator ausgelöst wird, kann der Cleanup-Code noch ausgeführt werden. Danach kann der Genera- tor dann keine weiteren Werte mehr erzeugen. Im Beispiel wird eine Datei geöffnet und eine Zeile ausgelesen. Wenn der Generator beendet wird, wer- den die noch nicht gelesenen Zeilen zurück in die Datei geschrieben. 3935042698_v01.book Seite 52 Freitag, 14. Oktober 2005 3:31 15
Die Zukunft 53 1.7.2 PEP 343: "Anonymous Block Redux and Generator Enhancements" Vor allem die Erweiterungen zur Finalisierung von Generatoren dienen der Implementation von PEP 343. Daher folgt hier noch ein kurzer Blick auf die neuen Features, die dort vorgeschlagen werden. Dieses PEP hat aber nur in- sofern mit Generatoren zu tun, als diese benutzt werden, um so genannte Blockvorlagen zu erstellen. Allen, die sich eingehender mit diesem Thema befassen wollen, sei das Studium von PEP 343 und der referenzierten Lite- ratur empfohlen. Mit Hilfe von Blockvorlagen soll es gelingen, wiederkehrende Muster in Programmen, die sich sonst nur schlecht abstrahieren lassen, kompakt und verständlich (eben "pythonic") darzustellen. Nehmen wir als Beispiel eine Datenbanktransaktion. Tritt während der Transaktion im with-Block eine unbehandelte Ausnahme auf, dann wird der except-Block ausgeführt, der hier ein Rollback der Transaktion vollzieht. Wenn kein Fehler auftritt, wird die Transaktion angenommen. def keepUnreadEntries(filename): infile = file(filename, "r") try:for line in infile: yield line finally: lines = list(infile) infile.close() outfile = file(, "w") for line in lines: outfile.write(line) outfile.close() @with_template def transactional(db): db.begin() try:yield ... 3935042698_v01.book Seite 53 Freitag, 14. Oktober 2005 3:31 15
1 -- Generatoren in Python 54 Die Ausnahmebehandlung mit Rollback oder Commit tritt an vielen Stellen im Programm auf, aber immer muss der ganze Block mit try-except-else eingefasst werden. Auch hätte man die Zeitmessung, die zum Benchmarken der verschiedenen Codestücke bei den Generator-Ausdrücken dient, so viel kompakter schrei- ben können. Beim Betreten des with-Blocks wird eine Instanz des übergebenen Genera- tors erstellt und gestartet. Der Generator kann einen Wert zurückgeben, der mit dem optionalen as-Keyword in einer Variablen gespeichert werden kann und für den with-Block gültig ist. Beim Verlassen des with-Blocks wird die Generatorinstanz ein weiteres Mal aufgerufen und der Aufräumcode (nach dem yield) ausgeführt. Danach muss der Generator beendet sein. Diese except: db.rollback() else:db.commit() with transactional(conn): cursor = conn.cursor() cursor.execute("INSERT ....") ... f = file("/datei/die/es/nicht/gibt", "r") # IOException! ... import time @with_template def timing(divisor, label): start = time.time() yield end = time.time() print " * %s: %.4fs pro Durchlauf" % (label, (end - start) / divisor) with timing(iterations, "some code"): ... 3935042698_v01.book Seite 54 Freitag, 14. Oktober 2005 3:31 15
Die Zukunft 55 Aufrufe übernimmt der Dekorator @with_template. Er prüft auch, ob nach dem zweiten Aufruf eine StopIteration-Ausnahme auftritt. Generatoren, die mehr als einen Wert erzeugen, sind daher in diesem Kontext ungültig und führen zu einer Ausnahme. Anmerkungen Für beide PEPs existieren zurzeit schon Beispielimplementationen. Trotz- dem sind die hier gezeigten Codebeispiele mit Vorsicht zu genießen, denn es ist sehr wahrscheinlich, dass sich bis zur endgültigen Implementation so- wohl die Syntax als auch die Semantik der verwendeten Funktionen ändern. Allerdings sind die beiden PEPs von Guido van Rossum selbst (mit-)ge- schrieben und Ergebnis einer langen Evolution über mehrere PEPs hinweg. Daher kann man annehmen, dass die darin präsentierten Erweiterungsvor- schläge technisch ausgereift sind und eine breite Unterstützung bei den Ent- wicklern von CPython selbst erfahren. 3935042698_v01.book Seite 55 Freitag, 14. Oktober 2005 3:31 15
3935042698_v01.book Seite 56 Freitag, 14. Oktober 2005 3:31 15
57 2 Objektorientierte Programmierung Von Michael Weigend In der Denkweise der objektorientierten Programmierung ist ein Programm ein System interagierender Objekte, das einen Ausschnitt der realen Welt modelliert. In diesem Kapitel geht es darum, wie man mit Python Konzepte der Objektorientierung umsetzen kann. 2.1 Python -- eine objektorientierte Programmiersprache Ein objektorientiertes Programm stellt man sich als System von Objekten vor, die untereinander Botschaften austauschen. Ein Objekt kann einen rea- len Gegenstand (z.B. Geldschein, Auto, Chemikalienflasche), eine Person, ein Ereignis (z.B. das Anklicken einer Schaltfläche, das Auftreten eines Laufzeitfehlers), eine physikalische Größe (Masse, Volumen) oder sonst ir- gendein abstraktes Konzept (Zahl, Text, soziale Gruppe) darstellen. In der Denkweise der OOP wird eine Programmentwicklung meist als Mo- dellierung eines Ausschnitts der Wirklichkeit angesehen. Allgemein gesagt sind in einem Objekt Daten und Operationen zu einer Einheit zusammenge- fasst. Drei Merkmale lassen sich herausstellen: 1. Ein Objekt befindet sich immer in einem Zustand. Es besitzt Attribute, die bei der Initialisierung mit Anfangswerten belegt werden. Manche dieser Attribute können von außen abgefragt und geändert werden. Die Belegung der Attribute definiert den Zustand des Objekts. 2. Ein Objekt zeigt Verhalten. Es beherrscht ein Repertoire von Operatio- nen (Methoden), die zum Teil von außen aufgerufen werden können. Man sagt dann: Das Objekt empfängt eine Botschaft und führt den darin enthaltenen Auftrag aus. 3. Ein Objekt hat eine Identität. Es kann eindeutig von anderen Objekten unterschieden werden. 3935042698_v01.book Seite 57 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 58 Verhalten und Struktur eines Objekts sind in einer Klasse (class) festlegt. Man kann sich eine Klasse als Bauplan für Objekte vorstellen. Objekte wer- den oft als Instanzen oder Inkarnationen einer Klasse bezeichnet. Python enthält vorgefertigte Klassen, die die Typen der Standard-Typhierarchie im- plementieren. Der Unterschied zwischen Typ und Klasse liegt darin, dass ein Typ allein das äußerlich sichtbare Verhalten eines Objekts beschreibt, die Implementierung aber völlig abkapselt. Python ist strikt objektorientiert. Zahlen, Zeichenketten, Listen, Ereignisse, Funktionen ja sogar Klassen sind Objekte. Mit type() kann man den Typ ei- nes Objekts (und damit auch die Bezeichnung der Klasse) abfragen: Python-Objekte sind introspektiv. Das heißt, sie beschreiben sich selbst. Zum Beispiel besitzt jedes Objekt ein Attribut, das die Bezeichnung seiner Klasse enthält. 2.2 Eine Klasse definieren -- die class-Anweisung Eine Klassendefinition setzt sich aus zwei Teilen zusammen: 1. Der Kopf beginnt mit dem Schlüsselwort class und endet mit einem Dop- pelpunkt. 2. Der Körper ist ein Anweisungsblock (eingerückt), der folgende Kompo- nenten enthalten kann: Klassenattribute, Methodendefinitionen, Anwei- sungen zur Definition von statischen Methoden, Klassenmethoden und Properties. Eine minimale, syntaktisch korrekte Klassendefinition, die freilich keinerlei Nutzen hat, ist folgende: >>> type(1) <type 'int'> >>>a=1 >>> a.__class__ <type 'int'> 3935042698_v01.book Seite 58 Freitag, 14. Oktober 2005 3:31 15
Eine Klasse definieren -- die class-Anweisung 59 Allgemein hat der Kopf einer Klassendefinition folgenden syntaktischen Aufbau: Nach dem Klassennamen können optional in Klammern die Namen von Basisklassen aufgeführt werden, von denen die neue Klasse abgeleitet wird. Die neue Klasse erbt Attribute und Methoden der angegebenen Basisklas- sen. Dazu später mehr. Beispiele für syntaktisch korrekte Klassendefini- tionsköpfe sind: New-Style-Klassen In modernem Python-Programmierstil vermeidet man die erste Version des Kopfs und verwendet zumindest die Standardklasse object (kleingeschrie- ben!) als Basisklasse, sofern die neue Klasse nicht von einer spezielleren Klasse abgeleitet werden soll. Die Klasse object enthält (versteckt) einige grundlegende Attribute und Me- thoden, die jede Python-Klasse benötigt. Alle Klassen der Python-Standard- typhierarchie (int, float, list, ...) sind von der Basisklasse object abgeleitet. Abkömmlinge der Basisklasse object bezeichnet man als New-Style-Klas- sen. Gegenwärtig ist es noch möglich, Klassen zu definieren, die nicht von object abstammen (klassische Klassen oder Old-Style-Klassen). Ab der Python-Version 3.0 werden voraussichtlich nur noch New-Style-Klassen unterstützt. class C: pass class Klassenname [(Basisklasse1[, Basisklasse2, ...])]: class C: class C (Basisklasse): class C (Basisklasse1, Basisklasse2): class C (object): ... 3935042698_v01.book Seite 59 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 60 2.3 Einführendes Beispiel: Modell einer Ampel Nach der Philosophie der objektorientierten Programmierung sind Klassen Modelle für Dinge der Welt. Wir modellieren nun eine Verkehrsampel durch die Klasse TrafficLight. #1: In der Kopfzeile wird die Klasse TrafficLight als Unterklasse der Stan- dard-Basisklasse object definiert. #2: Darunter definiert man einen Docstring mit einer kurzen Erklärung. Er hat für die Funktionalität der Klasse keine Bedeutung. Wenn der Docstring über mehrere Zeilen gehen soll, verwendet man dreifache Anführungszeichen. #3: In der __init__()-Methode werden Objektattribute und ihre Anfangs- werte festgelegt. Diese Methode wird aufgerufen, wenn ein Objekt der Klas- se instanziert wird. Der erste Parameter self repräsentiert das aktuelle Ob- jekt. Der Name ist eigentlich beliebig, aber üblicherweise verwendet man self. #4: Objekte der Klasse TrafficLight besitzen zwei Attribute, die in dieser und der nächsten Zeile erzeugt und mit Anfangswerten belegt werden: Das Attribut states enthält eine Liste von Zuständen, in denen sich eine Ampel im normalen Betrieb befinden kann. Der Zustand ist erkennbar an den Lich- tern, die die Ampel gerade anzeigt. Die Zustände sind in der Reihenfolge aufgeführt, wie sie bei einer Ampel auftreten. Das Attribut index ist die Nummer des aktuellen Zustands in der Liste states. class TrafficLight (object): #1 "Klasse modelliert eine Verkehrsampel" #2 def __init__ (self): #3 self.states = ["rot", "rot gelb", "gruen", "gelb"] #4 self.index = 0 def change (self): #5 if self.index < len(self.states) - 1: self.index += 1 else: self.index = 0 return self.states[self.index] 3935042698_v01.book Seite 60 Freitag, 14. Oktober 2005 3:31 15
Eine Klasse testen 61 #5: Die Methode hat als ersten und einzigen formalen Parameter einen Namen für das aktuelle Objekt (self). Sie braucht diesen Namen, um auf die Attribute index und states zugreifen zu können. Ein Aufruf der Methode be- wirkt, dass die Ampel in den nächsten Zustand wechselt. Zurückgegeben wird ein String aus der Liste states, der den neuen Zustand repräsentiert. 2.4 Eine Klasse testen Um diese Klasse zu testen, können Sie so vorgehen: Sie schreiben die Klas- sendefinition in einem IDLE-Editorfenster auf, speichern sie in einer Datei als Modul ab (file|save as) und führen das Modul aus (Modul|run). Wie Funktionsdefinitionen sind auch Klassendefinitionen -- Anweisungen, die ausgeführt werden können. Sofern der Python-Interpreter keinen Fehler er- kennt, erscheint das Shell-Fenster mit der Meldung In der Python-Shell (interaktiver Modus) instanzieren Sie mit folgender An- weisung ein Objekt der Klasse TrafficLight: Die Methode change() wird in der üblichen Punktnotation Objekt.Methode() aufgerufen. Sie liefert einen String mit dem neuen Zustand der Ampel. Be- achten Sie, dass change() ohne Argument aufgerufen wird. Der erste forma- le Parameter self der Methodendefinition wird weggelassen. >>> ================= RESTART ================== >>> >>> t = TrafficLight() >>> t.change() 'rot gelb' >>> t.change() 'gruen' >>> t.change() 'gelb' >>> t.change() 'rot' 3935042698_v01.book Seite 61 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 62 Bei größeren Projekten gehört es zum guten Stil, jede Klasse in einer eige- nen Datei abzuspeichern. Damit gehört die Klasse zu einem Modul, das mit einer import-Anweisung importiert werden kann. Anstatt die Klasse im in- teraktiven Modus zu testen, kann man auch Testaufrufe hinter die Klassen- definition an das Ende des Moduls setzen. Beachten Sie, dass Sie jetzt print- Anweisungen verwenden müssen, damit zurückgegebene Objekte auf dem Bildschirm dargestellt werden. Beispiel: Die if-Anweisung bewirkt, dass die Testanweisungen nur dann ausgeführt werden, wenn das Modul direkt vom Interpreter ausgeführt wird. In diesem Fall ist der Name des Moduls "__main__". Im Shell-Fenster erscheint bei diesem Beispiel folgende Ausgabe: Wird das Modul dagegen von einem anderen Modul importiert, hat es einen anderen Namen und die Testanweisungen werden einfach übersprungen. class TrafficLight (object): ... if __name__ == "__main__": t = TrafficLight() print t.change() print t.change() >>> ================== RESTART ================= >>> rotgelb gruen >>> 3935042698_v01.book Seite 62 Freitag, 14. Oktober 2005 3:31 15
Attribute und Methoden definieren 63 2.5 Attribute und Methoden definieren 2.5.1 Weiterführendes Beispiel: Modell eines Volumens Wir definieren eine Klasse, die Volumina (Rauminhalte) modelliert. Bei- spiele für Volumina sind 1,0 l (Inhalt einer Cola-Flasche), 200 ml (typischer Inhalt eines Bierglases außerhalb von Bayern) oder 1,08 ∗ 1023cbm (Volu- men des Planeten Erde). Das heißt, eine Volumenangabe besteht immer aus einer Zahl und einer Volumeneinheit (ml: Milliliter, l: Liter, cbm: Kubik- meter). Abbildung 2.1 zeigt ein UML-Klassendiagramm der Klasse Vo l u m e n . In der obersten Zeile in Fettdruck steht der Name, darunter werden die Attribute und Methoden der Klasse aufgeführt. Das Attribut liter wird hier durch den vorangestellten Slash / als abgeleitetes Attribut beschrieben. Das heißt, es enthält keine eigenen unabhängigen Werte, sondern wird durch eine Metho- de implementiert, die aus anderen Attributen den aktuellen Wert berechnet. Abb. 2.1: UML-Klassendiagramm Volumen lpe wert einheit / liter add() __repr__() Klassenname Attribute Methoden Klassenattribut abgeleitetes Attribut 3935042698_v01.book Seite 63 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 64 Geben Sie die class-Anweisung im IDLE-Editorfenster ein und führen Sie das Modul aus. Im interaktiven Shell-Fenster können Sie nun die Klasse tes- ten. Erzeugen Sie zwei Objekte der Klasse Vo l u m e n und addieren Sie die beiden Volumina: Sie erkennen die Arbeitsweise der Methode add(). Sie liefert ein neues Vo- lumen-Objekt, das die Summe zweier Volumina repräsentiert. Dabei wird die Volumeneinheit desjenigen Objekts verwendet, an das die Botschaft ge- schickt worden ist (hier: v1). In diesem Fall ist das Ergebnis 1,005 Liter. class Volumen (object): #1 "Modelliert Volumina" lpe = {"l": 1.0, "ml": 0.001, "cbm": 1000.0} #2 def __init__(self, value, unit): #3 if unit in self.lpe.keys(): self.wert = value #4 self.einheit = unit def liter(self): #5 return self.wert * self.lpe[self.einheit] def add (self, other): #6 summe = self.liter() + other.liter() #7 return Volumen(summe/self.lpe[self.einheit], self.einheit) def __str__ (self): #8 return str(self.wert) + " " + self.einheit >>> v1 = Volumen (1, "l") >>> v1 1l >>> v2 = Volumen (5, "ml") >>> v2 5ml >>> v1.add(v2) 1.005 l 3935042698_v01.book Seite 64 Freitag, 14. Oktober 2005 3:31 15
Klassenattribute und Objektattribute 65 2.5.2 Initialisierung eines Objekts -- die Methode __init__() Die Methode __init__() dient der Initialisierung eines Objekts. In ihr wer- den die Objektattribute definiert und mit Anfangswerten belegt. Das erste Argument in der Parameterliste repräsentiert das aktuelle Objekt. Der Name ist beliebig, aber üblicherweise nennt man es self. Die weiteren Parameter spezifizieren Anfangswerte für Attribute, die bei der Instanzierung eines Objekts übergeben werden. Um auf ein Objektattribut innerhalb der Klas- sendefinition zuzugreifen, verwendet man das Format self.Attribut, in die- sem Beispiel also self.wert und self.einheit. In den ersten Zeilen des obigen Beispieldialogs im Shell-Fenster werden zwei Objekte der Klasse Vo l u m e n erzeugt. Wenn die __init__()-Prozedur ei- ner Klasse C den Kopf besitzt, lautet der Aufruf bei der Instanzierung eines Objekts Der Konstruktoraufruf hat also mindestens ein Argument weniger als die Definition der __init__()-Prozedur. Den Namen des Objekts (der neuen In- stanz) lässt man beim Aufruf weg. 2.6 Klassenattribute und Objektattribute Attribute können Merkmale eines einzelnen Objekts (Objektattribut) oder ein Merkmal der ganzen Klasse (Klassenattribut) darstellen. Die Attribute wert und einheit der Klasse Vo l u m e n des obigen Beispiels sind Objektattri- bute. Ihre Belegung kann bei jeder Instanz anders sein. Objektattribute re- präsentieren den Zustand eines Objekts. Verschiedene Objekte ein und der- selben Klasse unterscheiden sich in der Regel in ihren Attributen. __init__(self, arg1, arg2, ...): C(arg1, arg2, ...) 3935042698_v01.book Seite 65 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 66 Klassenattribute dagegen sind für alle Objekte einer Klasse gleich. Im UML-Klassendiagramm (Abbildung 2.1) werden Klassenattribute unterstri- chen. Im Beispielskript ist lpe (Liter pro Einheit) ein Klassenattribut (#2). Es han- delt sich um eine Umrechnungstabelle für Volumina, genauer gesagt ein Dictionary, das zu einer Einheit die zugehörige Zahl von Litern angibt. So ist lpe["cbm"] die Zahl 1000 zugeordnet. Das heißt, 1 Kubikmeter (cbm) ent- hält 1000 Liter. Offensichtlich ist das Dictionary lpe ein Merkmal des ge- samten Maßsystems für Volumina. Klassenattribute werden innerhalb des Körpers einer Klasse, aber außerhalb einer Methode definiert. Das Format ist: Es wird also bei der Definition kein self verwendet. Auf die Klassen- und Objektattribute kann man von außen in der üblichen Punktnotation zugreifen: Beispiel: Attribut = Wert Klasse.Attribut Objekt.Attribut >>> v1 = Volumen(1, "l") >>> v1.einheit # Objektattribut 'l' >>> v1.wert 1>>> Volumen.lpe # Klassenattribut {'ml': 0.001, 'l': 1, 'cbm': 1000} 3935042698_v01.book Seite 66 Freitag, 14. Oktober 2005 3:31 15
Klassenattribute und Objektattribute 67 Eine Besonderheit bei Python ist, dass einem Objekt nach seiner Instanzie- rung noch weitere Attribute zugefügt werden können: Ebenso können Attribute durch eine del-Anweisung gelöscht werden: Der dynamische Charakter eines Objekts birgt ein Fehlerrisiko, das durch geeignete programmtechnische Maßnahmen abgefangen werden sollte. Wenn bei einem Zugriff versehentlich der Name eines Attributs falsch ge- schrieben wird, gibt es keine Fehlermeldung. Der Interpreter erzeugt einfach ein neues Attribut. >>> v1 = Volumen(10, "cbm") >>> v1.stoff = "Benzin" >>> v1.stoff 'Benzin' >>> >>> del v1.stoff >>> v1.stoff Traceback (most recent call last): File "<pyshell#43>", line 1, in -toplevel- v1.stoff AttributeError: 'Volumen' object has no attribute 'stoff' >>> >>> v1.wert = 200 >>> v1.wert 200 >>> v1.wrt = 300 # Schreibfehler >>> v1.wert # wert wurde nicht geaendert 200 >>> v1.wrt # neues Attribut 300 3935042698_v01.book Seite 67 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 68 2.7 Methoden Methoden sind Operationen, die Objekte ausführen können. Sie werden wie Funktionen definiert. Die einzigen Unterschiede sind: 1. Methoden werden innerhalb einer Klasse definiert. 2. Die Liste der formalen Parameter im Methodenkopf enthält als erstes Argument einen Namen für das aktuelle Objekt (self). Diesen Namen braucht man, wenn man innerhalb des Methodenkörpers auf Objektattri- bute zugreifen oder eine andere Methode der Klasse aufrufen möchte (Zeile #7 im Beispiel). 3. Methoden werden im Format Objekt.Methode(...) aufgerufen. Dabei ent- hält die Parameterliste des Aufrufs ein Argument weniger als die Liste der formalen Parameter im Kopf der Methodendefinition (self wird beim Aufruf weggelassen). Magische Methoden Klassen enthalten einige spezielle Methoden, deren Namen mit doppelten Unterstrichen beginnen und enden. Sie sind für besondere Zwecke gedacht und werden manchmal "magisch" genannt, weil sie "wie durch Zauberei" das Verhalten des Interpreters beeinflussen (siehe Tabelle 2.1). Ein Beispiel ist die __init__()-Methode. Die Klasse Vo l u m e n enthält noch ein zweites Beispiel, die Methode __str__(). Sie liefert einen String mit einer lesbaren Repräsentation des Objekts. Man kann diese Methode natürlich direkt auf- rufen. Hauptsächlich wird sie aber vom Interpreter verwendet, z.B. wenn er eine print-Anweisung ausführt oder im interaktiven Modus ein Objekt auf dem Bildschirm ausgibt. >>> v1 = Volume(200, "ml") >>> v1.__str__() '200 ml' >>> print v1 200 ml >>> v1 200 ml 3935042698_v01.book Seite 68 Freitag, 14. Oktober 2005 3:31 15
Methoden 69 Überladen von Operatoren Eine wichtige Verwendung magischer Methoden ist das Überladen (over- loading) von Operatoren und Funktionsbezeichnern. Damit ist gemeint, dass der gleiche Name für "ähnliche" Operationen auf Objekte unterschiedlicher Klassen angewendet werden kann. Man spricht auch von Polymorphie oder Polymorphismus. So wird beispielsweise der Operator + sowohl für die arithmetische Addition von Zahlen als auch für die Konkatenation (Anein- anderhängung) von Strings verwendet. Für Operatoren sind nun Methodennamen reserviert, die Sie zum Überladen verwenden können (siehe Tabelle 2.1). Beispielsweise ist der Name __add__ für den Plusoperator + reserviert. Wenn Sie innerhalb einer Klasse eine Methode mit diesem Namen definieren, lässt sich der Plusoperator auch auf Objekte dieser Klasse anwenden. Entsprechend kann man durch eine Methode namens __mul__ den Multiplikationsoperator * überladen. Wir ändern die Klasse Volumen aus dem obigen Beispiel etwas ab und er- möglichen Addition und Multiplikation mit + und *. class Volumen(object): ... def __add__(self, other): # Ueberladen von + ... def __mul__(self, faktor): # Ueberladen von * return Volumen(self.wert*faktor, self.einheit) ... >>> Volumen(200, "ml") + Volumen(1, "l") 1200.0 ml >>> Volumen(2, "ml") * 2 4ml 3935042698_v01.book Seite 69 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 70 2.8 Vererbung Von einer Klasse können Unterklassen abgeleitet werden. Im Kopf der De- finition der Unterklasse schreibt man hinter den Klassennamen die Namen einer oder mehrerer Basisklassen (Oberklassen). Wir betrachten zunächst den Fall einer Einfachvererbung (nur eine Basisklasse). Die Unterklasse erbt alle Attribute und Methoden ihrer Basisklasse. Darüber hinaus können zu- sätzliche Attribute und Methoden definiert werden. Dann ist die neue Klasse eine Erweiterung der Basisklasse. Das Vererbungsprinzip hat einen ent- scheidenden Vorteil: Man kann eine Basisklasse mit sehr allgemeinen und grundlegenden Attributen und Methoden definieren und dann im zweiten Schritt davon mehrere unterschiedliche spezialisierte Klassen ableiten. Die folgende Klasse Portion ist eine Erweiterung der Klasse Vo l u m e n . Sie repräsentiert ein bestimmtes Volumen eines Stoffs. Abbildung 2.2 zeigt ein UML-Klassendiagramm. Die Beziehung zwischen Unter- und Oberklasse wird durch einen Pfeil mit ungefüllter Spitze dargestellt. Methode Erklärung __add__(self, other) Überladen des Plusoperators + __contains__(self, item) Überladen des in-Operators für eine Kollek- tion. Zurückgegeben wird Tr u e , falls das Objekt self das Objekt item enthält, ansonsten False. __eq__(self, other) Überladen des Gleichheitsoperators ==. Zurückgegeben wird Tr u e , falls die Objekte self und other gleich sind, ansonsten False. __len__(self) Überladen der Standardfunktion len() __mul__(self, other) Überladen des Multiplikationsoperators * Tabelle 2.1: Einige reservierte Namen für magische Methoden (Auswahl) 3935042698_v01.book Seite 70 Freitag, 14. Oktober 2005 3:31 15
Vererbung 71 Abb. 2.2: UML-Klassendiagramm für eine aus zwei Klassen bestehende Klassenhierarchie. class Portion(Volumen): def __init__ (self, wert, einheit, stoff): #1 Volumen.__init__(self, wert, einheit) #2 self.stoff = stoff def __add__(self, other): #3 assert self.stoff == other.stoff summe = Volumen.__add__(self, other) #4 return Portion (summe.wert, summe.einheit, self.stoff) def __str__(self): #5 return Volumen.__str__(self) + " " + \ self.stoff Volumen lpe wert einheit / liter __add__() __mul__() __repr__() Portion stoff __add__() __repr__() Basisklasse abgeleitete Klasse, Spezialisierung überschriebene (erweiterte) Methoden 3935042698_v01.book Seite 71 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 72 Kommentar: #1: Die neue Klasse besitzt ein zusätzliches Attribut stoff, das eine Zeichen- kette mit einer Beschreibung des Stoffs enthält. Deshalb muss die geerbte __init__()-Methode neu definiert werden. Man sagt, sie wird überschrieben. #2: Zunächst wird die Initialisierungsprozedur der Basisklasse Vo l u m e n auf- gerufen, die sich um die Initialisierung der geerbten Attribute kümmert. An- schließend wird das neue Attribut stoff mit einem Wert belegt. #3: Die geerbte Methode __add__() wird überschrieben und erweitert. Nur wenn die beiden Stoffe gleich sind, können sie sinnvollerweise addiert wer- den. Wenn diese Bedingung nicht zutrifft, verursacht das assert-Statement eine Exception. #4: Die __add__()-Methode der Oberklasse Vo l u m e n wird aufgerufen. Sie akzeptiert Portion-Objekte als Argumente, gibt aber kein Objekt der Klasse Portion, sondern ein Vo l u m e n -Objekt zurück. #5: Wir erweitern die geerbte Methode __str__(), so dass die textuelle Re- präsentation von Portion-Objekten auch die Bezeichnung des Stoffs enthält. Die von Vo l u m e n geerbte Methode __mul__() kann unverändert übernom- men werden. Bei ihrem Aufruf sucht der Interpreter zunächst in der Klas- sendefinition von Portion. Da er dort keine Definition von __mul__() findet, sucht er in der Oberklasse Vo l u m e n weiter. 2.9 Mehrfachvererbung und Method Resolution Order Python erlaubt Mehrfachvererbung, d.h., eine Klasse kann mehrere Basis- klassen besitzen. Ein Problem entsteht, wenn in den Oberklassen mehrere Methoden gleichen Namens definiert sind. Die Frage ist dann, welche dieser Methoden der Interpreter ausführen soll. >>> a1 = Portion(20, "l", "Wasser") >>> a2 = Portion(30, "l", "Wasser") >>>a1+a2 50.0 l Wasser >>>a1*2 40.0 l Wasser 3935042698_v01.book Seite 72 Freitag, 14. Oktober 2005 3:31 15
Mehrfachvererbung und Method Resolution Order 73 Abb. 2.3: Klassenhierarchie mit Diamond-Struktur Wir betrachten ein besonders kniffliges Beispiel einer Vererbungsstruktur, die man auch als Diamond-Struktur (diamond: engl. Karo) bezeichnet. Ab- bildung 2.3 zeigt das UML-Klassendiagramm. class Stoff(object): def __init__(self, bezeichnung): self.bezeichnung = bezeichnung def druckeEtikett (self): print self.bezeichnung class Stoffportion(Stoff): def __init__(self, bezeichnung, masse): Stoff.__init__(self, bezeichnung) self.masse = masse class Gefahrstoff (Stoff): def __init__(self, bezeichnung, gefahr): Stoff.__init__(self, bezeichnung) self.gefahr = gefahr ... Stoff bezeichnung druckeEtikett() Stoffportion masse Gefahrstoff gefahr druckeEtikett() Chemikalie Salzsäure Salzsäure (ätzend) 3935042698_v01.book Seite 73 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 74 Ein Objekt der Klasse Stoff repräsentiert einen Stoff, dessen Bezeichnung im Attribut bezeichnung gespeichert wird. Das Objekt besitzt die Methode druckeEtikett(), mit der ein Etikett für einen Behälter mit diesem Stoff aus- gedruckt wird. Von dieser Klasse werden zwei Spezialisierungen abgeleitet: Die Klasse Stoffportion modelliert Portionen von Stoffen. Zusätzlich zur Stoffbezeich- nung wird auch die Masse einer Stoffportion repräsentiert. Da auf den Eti- ketten von Vorratsbehältern keine Massenangaben benötigt werden (der In- halt ändert sich ja ständig), wird in dieser Klasse keine erweiterte Methode zum Ausdrucken von Etiketten definiert, sondern die entsprechende Metho- de der Basisklasse verwendet. Die zweite Klasse, die von der Basisklasse Stoff abgeleitet wird, repräsen- tiert Gefahrstoffe. Objekte dieser Klasse besitzen als zusätzliches Attribut eine Bezeichnung der Gefahr, die von diesem Stoff ausgeht (z.B. ätzend). Weil Etiketten von Stoffbehältern Gefahrenhinweise tragen müssen, wurde die Methode druckeEtikett() entsprechend erweitert. Die vierte Klasse Chemikalie schließlich modelliert Portionen von Gefahr- stoffen und wird von Stoffportion und Gefahrstoff abgeleitet. Sie enthält kei- ne eigene Methode druckeEtikett(), sondern erbt sie. Aber welche der beiden Methoden wird ausgeführt, wenn ein Objekt der Klasse Chemikalie den Auftrag erhält, ein Etikett zu drucken? Probieren wir es aus: def druckeEtikett (self): print "%s(%s)" % (self.bezeichnung, self.gefahr) class Chemikalie (Stoffportion, Gefahrstoff): def __init__(self, bezeichnung, gefahr, masse): Stoffportion.__init__(self, bezeichnung, masse) Gefahrstoff.__init__(self, bezeichnung, gefahr) >>> stoff1 = Chemikalie("Salzs\xe4ure", "\xe4tzend", 1000) >>> stoff1.druckeEtikett() Salzsäure(ätzend) 3935042698_v01.book Seite 74 Freitag, 14. Oktober 2005 3:31 15
Mehrfachvererbung und Method Resolution Order 75 Offenbar ist hier die Methode der Eltern-Klasse Gefahrstoff ausgeführt wor- den und nicht die der Großeltern-Klasse Stoff. Das ist auch das gewünschte Ergebnis. Man möchte in solchen Fällen auf eine Oberklasse zurückgreifen, die der Klasse des angesprochenen Objekts am ähnlichsten ist, d.h., die in der Klassenhierarchie am nächsten liegt. Übrigens: Wenn wir die Klasse Stoff als Old-Style-Klasse definiert hätten, hätten wir ein anderes Ergebnis erhalten. Dann hätte der Interpreter die druckeEtikett()-Methode der Klasse Stoff ausgewählt. Probieren Sie es aus! Sie brauchen nur die Kopfzeile der Klassendefinition von Stoff zu ändern: Die Reihenfolge, in der der Interpreter in einer Klassenhierarchie nach Me- thoden sucht, bezeichnet man als Method Resolution Order (MRO). Die MRO wird bereits bei der Definition der Klasse vorausberechnet und im At- tribut __mro__ gepeichert. Seit der Version 2.3 verwendet Python dafür ei- nen Algorithmus namens C3, der von Kim Barret u.a. (1996) entwickelt worden ist. Man kann sich die MRO einer Klasse ansehen (Python-Klassen sind intro- spektiv). Beachten Sie, dass es sich um ein Attribut einer Klasse und nicht einer Instanz handelt. class Stoff: ... >>> stoff1 = Chemikalie("Salzs\xe4ure", "\xe4tzend", 1000) >>> stoff1.druckeEtikett() Salzsäure >>> Chemikalie.__mro__ (<class '__main__.Chemikalie'>, <class '__main__.Stoffportion'>, <class '__main__.Gefahrstoff'>, <class '__main__.Stoff'>, <type 'object'>) 3935042698_v01.book Seite 75 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 76 2.10 Ableiten oder aggregieren? In diesem Abschnitt stellen wir zwei Implementierungen von Klassen einan- der gegenüber, die gerichtete Graphen repräsentieren. In der ersten Version definieren wir eine Klasse Graph als eine Spezialisierung des Standardtyps dict und verwenden das Vererbungsprinzip. In der zweiten Implementierung definieren wir eine Klasse, die ein Dictionary als Attribut enthält. Eine solche Struktur bezeichnet man als Aggregat. Ein Objekt der Klasse Graph ist hier ein Ganzes, das ein Dictionary-Objekt als Teil enthält. Abbildung 2.4 zeigt UML-Klassendiagramme dieser beiden Lösungsansätze. Abb. 2.4: Abgeleitete Klasse und Aggregat Ein gerichteter Graph besteht aus einer Menge von Knoten V (vertices) und einer Menge von gerichteten Kanten E (edges), die einen Teil der Knoten verbinden. Abb. 2.5 zeigt ein Beispiel. Abb. 2.5: Ein gerichteter Graph mit vier Knoten Bei Python kann man einen Graphen durch ein Dictionary repräsentieren, das jedem Knoten eine Adjazenzliste zuordnet. Eine Adjazenzliste eines Knotens v ist eine Liste aller Knoten, die von v aus über eine Kante direkt er- dict dict Graph Graph 1 3 4 2 G=(V,E) V={1,2,3,4) E={(1,2),(1,3),(2,1),(2,3),(2,4)} 3935042698_v01.book Seite 76 Freitag, 14. Oktober 2005 3:31 15
Ableiten oder aggregieren? 77 reichbar sind. Es bietet sich an, die Klasse Graph von der built-in-Klasse dict abzuleiten. Das folgende Listing zeigt eine Lösung: Kommentar: #1: Ein Graph wird immer als leeres Dictionary instanziert. Später können Knoten und Kanten eingefügt werden. Innerhalb der __init__()-Prozedur wird die __init__()-Methode der Basisklasse dict als ungebundene Methode aufgerufen. Sie erhält als Argumente das aktuelle Objekt self und ein leeres Dictionary. #2: Sofern das Objekt nicht schon einen Knoten namens v enthält, wird ein neuer Knoten mit leerer Adjazenzliste eingetragen. Beachten Sie, dass im Ausdruck v in self geprüft wird, ob v in der Menge der Schlüssel (keys) des Dictionarys vorkommt. class Graph(dict): """ Graph als Unterklasse von dict """ def __init__(self): dict.__init__(self, {}) #1 def insertVertex(self, v): #2 if v not in self: self[v] =[] def insertEdge(self, v1, v2): #3 if (v1 in self) and (v2 in self) and \ (v2 not in self[v1]): self[v1].append(v2) def delVertex(self, v): #4 if v in self.d: del self.d[v] for adList in self.values(): adList.remove(v) def delEdge(self, v1, v2): #5 try: self[v1].remove(v2) except: pass 3935042698_v01.book Seite 77 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 78 #3: Sofern v1 und v2 Knoten des Graphen sind, wird v2 der Adjanzenzliste von v1 zugefügt. #4: Der Dictionary-Eintrag mit v1 als Schlüssel wird gelöscht. Außerdem wird v1 aus allen Adjazenzlisten entfernt. #5: Wir versuchen, v2 aus der Adjazenzliste von v1 zu entfernen. Falls das nicht gelingt (z.B. weil die Kante gar nicht existiert), passiert nichts. Im interaktiven Modus erzeugen wir nun einen Graphen und probieren eini- ge Anweisungen aus. Sie erkennen einen Vorteil der Vererbung: Man kann geerbte Dictionary- Methoden verwenden, ohne dass man sie in der Klasse Graph definieren musste. Graphen können mit update() erweitert, mit copy() kopiert und mit dem Operator == auf Gleichheit getestet werden. Genau darin liegt aber auch der Nachteil des Vererbungsprinzips. Die Klasse Graph gestattet auch Eingriffe, die zu einem inkonsistenten Zustand führen. So ist es möglich, mit einer del-Anweisung einen Knoten zu löschen, ohne dass gleichzeitig alle Kanten entfernt werden, die diesen Knoten enthalten >>> g=Graph() >>> g.update({1: [2, 3], 2: [1, 3, 4], 3: [], 4: []}) >>> g {1:[2,3],2:[1,3,4],3:[],4:[]} >>> g.insertEdge(4, 3) >>> g {1:[2,3],2:[1,3,4],3:[],4:[3]} >>> g1 = g.copy() >>> g1 is g # die Graphen sind nicht identisch ... False >>> g == g1 # ... aber gleich True >>>1ing True >>> del g[1] #1 >>> g {2: [1, 3, 4], 3: [], 4: [3]} >>> 3935042698_v01.book Seite 78 Freitag, 14. Oktober 2005 3:31 15
Ableiten oder aggregieren? 79 (#1). Wollte man derartige Fehler verursachende Zugriffe unterdrücken, so müsste man sämtliche problematischen Dictionary-Methoden überschrei- ben. Balzert weist darauf hin, dass die Vererbung im Widerspruch zum Ge- heimnisprinzip der Objektorientierung steht1. Bei der Ableitung einer Klas- se muss man Wissen über die innere Struktur der Basisklasse heranziehen. Das folgende Skript zeigt nun eine Implementierung der Klasse Graph durch Aggregatbildung. Sie enthält ein Dictionary mit Adjazenzlisten als Attribut namens __d. Die doppelten Unterstriche bewirken, dass man auf das Attribut von außen nicht zugreifen kann. Mehr dazu im nächsten Ab- schnitt. 1. Balzert, Heide: Lehrbuch der Objektmodellierung, Heidelberg Berlin 2004 class Graph(object): "Graph als Dictionary mit Adjazenzlisten" def __init__(self): self.__d={} def insertVertex(self, v): if v not in self.__d.keys(): self.__d[v] =[] def insertEdge(self, v1, v2): if (v1 in self.__d) and (v2 in self.__d) and \ (v2 not in self.__d[v1]): self.__d[v1].append(v2) def delVertex(self, v): if v in self.__d: del self.__d[v] for adList in self.__d.values(): adList.remove(v) def delEdge(self, v1, v2): try: self.__d[v1].remove(v2) except: pass ... 3935042698_v01.book Seite 79 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 80 2.11 Sichtbarkeit Ein zentraler Grundsatz in der objektorientierten Programmierung ist das Geheimnisprinzip. Ein Objekt soll Aufträge erledigen, aber so wenig wie möglich von seinem inneren Aufbau preisgeben. Nur einige Attribute und Methoden sind öffentlich, das heißt nach außen hin sichtbar, die anderen sind versteckt (privat). Wie im letzten Abschnitt gezeigt wurde, kann ein un- kontrollierter schreibender Zugriff auf ein Attribut das Objekt in einen in- konsistenten Zustand überführen. 2.11.1 Öffentliche und private Namen Bei Python lässt sich die Sichtbarkeit von Attributen und Methoden in ge- wissen Grenzen über ihre Namen kontrollieren. Private Namen beginnen mit mindestens zwei Unterstrichen und enden nicht mit Unterstrichen. Auf sie kann man nur innerhalb der Klassendefinition zugreifen. Von außen ist ein Zugriff so ohne weiteres nicht möglich. def __str__(self): return str(self.__d) >>> g = Graph() >>> g.insertVertex(1) >>> g.insertVertex(2) >>> g.insertEdge(1,2) >>> g {1: [2], 2: []} class A (object): def __init__ (self): self.__privat = "privat" self.oeffentlich ="oeffentlich" >>>a=A() >>> print a.oeffentlich oeffentlich >>> print a.__privat ... 3935042698_v01.book Seite 80 Freitag, 14. Oktober 2005 3:31 15
Sichtbarkeit 81 Allerdings können private Namen den Zugriff nicht wirklich verhindern. Das private Attribut kann man sichtbar machen, wenn man seinem Namen ein Präfix aus einem Unterstrich und dem Klassennamen voranstellt: Generell ist Restriktivität in der Python-Community eher verpönt ("We're all consenting adults here"). Der Hintergrund mag sein, dass Python eng mit Ideen der agilen Programmierung (z.B. Extreme Programming) verbunden ist. Hier geht man davon aus, dass das gesamte Team für den Programmtext verantwortlich ist ("collective ownership of code"). Weil jedes Teammit- glied an jeder Stelle Programmtext des Projektes verändern darf, erübrigen sich restriktive Schutzmechanismen. 2.11.2 Properties: Attribute mit Zugriff über get/set-Methoden Private Namen sind nur ein sehr grober Mechanismus der Zugriffskontrolle für Attribute. Zuweilen möchte man aber die Zugriffsrechte feiner einstel- len. Ein Attribut soll vielleicht von außen zwar gelesen, aber nicht geändert werden können. New-Style-Klassen bieten eine Möglichkeit, Attribute so zu definieren, dass man auf sie von außen scheinbar direkt zugreifen kann, der Zugriff aber von speziellen, in der Klasse definierten Methoden kontrolliert wird. Man geht wie folgt vor: 1. Alle Attribute sind privat, das heißt, ihre Namen beginnen mit doppelten Unterstrichen. 2. Für jedes Attribut, das von der Außenwelt gelesen werden darf, definiert man eine öffentliche Methode, die den Wert zurückgibt. Der Name der Traceback (most recent call last): File "<pyshell#10>", line 1, in -toplevel- print a.__privat AttributeError: 'A' object has no attribute '__privat' >>> print a._A__privat privat 3935042698_v01.book Seite 81 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 82 Methode ist beliebig, aber zur besseren Lesbarkeit wählt man einen Na- men der Form getAttribut. 3. Für jedes Attribut, das von außen verändert werden darf, definiert man eine öffentliche Methode mit einem Namen der Form setAttribute, die das Attribut verändern kann. Diese Methode sollte den neuen Attribut- wert zunächst kritisch überprüfen, bevor sie die Änderung vornimmt. Stimmt der Datentyp? Bringt die Änderung das Objekt in einen zulässi- gen Zustand? 4. Am Ende der Klassendefinition werden mit Hilfe der Funktion proper- ty() für die Objekte der Klasse so genannte Properties (Eigenschaften) definiert. Jede Property korrespondiert mit einem Attribut, das nach au- ßen sichtbar sein soll. Als Argumente werden die Namen der Zugriffs- methoden übergeben. Das folgende Skript zeigt ein Beispiel: class Label(object): def __init__(self): self.__text = "no name" self.__price = 0.0 def getPrice(self): #1 return self.__price def getText(self): return self.__text def setPrice(self, x): self.__price = float(x) #2 def setText(self, t): try: self.__text = t[:12] #3 except: self.__text = "no name" text = property(getText, setText) #4 price = property(getPrice, setPrice) 3935042698_v01.book Seite 82 Freitag, 14. Oktober 2005 3:31 15
Sichtbarkeit 83 Kommentar: #1: Die Methode liefert den aktuellen Wert des privaten Attributs __price. #2: Die Methode ermöglicht es, dass dem privaten Attribut __price ein neu- er Wert zugewiesen wird. Die Kontrolle besteht hier darin, dass das Attribut immer nur eine Gleitkommazahl erhält. Das ist der Unterschied zu einem di- rekten schreibenden Zugriff. #3: Dem Attribut __text wird eine Zeichenkette mit höchstens zwölf Zei- chen zugewiesen. Falls t ein Objekt eines völlig ungeeigneten Typs ist, das nicht in eine Zeichenkette umgewandelt werden kann, erhält __text den String "no name". #4: Hier wird eine öffentlich zugängliche Eigenschaft (Property) namens text definiert. Auf sie kann man über die Methode getText() lesend und über setText() schreibend zugreifen. Im interaktiven Modus probieren wir die Klasse Label aus. Sie können beo- bachten, dass man die Properties text und price wie Attribute anwendet, die Änderungen der internen Attribute jedoch durch die Zugriffsmethoden kon- trolliert werden. Die Funktion property() bedarf noch einiger Erläuterungen. Bei einem Auf- ruf können ihr maximal vier Argumente übergeben werden. Die vollständi- ge Signatur lautet: >>> label1=Label() # neues Label-Objekt >>> label1.text=123 # kein String! >>> label1.text 'no name' >>> label1.text="Zartbitterschokolade" >>> print label1.text # nur 12 Zeichen Zartbittersc >>> label1.price=1 # ganze Zahl >>> print label1.price # Gleitkommazahl 1.0 property(fget=None, fset=None, fdel=None, doc=None) 3935042698_v01.book Seite 83 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 84 Dabei ist fget der Name einer Methode für den lesenden Zugriff, fset der Name eine Methode für den schreibenden Zugriff und fdel der Name einer Methode, die das korrespondierende Attribut löschen kann. Das letzte Argu- ment ist ein Dokumentationsstring. Die Funktion verwendet Schlüsselwort-Argumente mit Default None. Man kann also Lese-, Schreib- und Löschmethoden in beliebiger Kombination angeben oder auch weglassen. Wenn in der property-Klausel keine Schreib- methode spezifiziert worden ist, gibt es beim Versuch eines schreibenden Zugriffs einen AttributError, wie das folgende Beispiel zeigt: Das Attribut kann gelesen, aber nicht verändert werden: class Const(object): def __init__(self, wert): self.__wert = wert def getWert(self): return self.__wert x = property(fget=getWert, doc = "nicht aenderbar" ) >>> a = Const(42) >>> a.x # Lesender Zugriff 42 >>>a.x=2 # schreibender Zugriff Traceback (most recent call last): File "<pyshell#16>", line 1, in -toplevel- a.x=2 AttributeError: can't set attribute >>> Const.x.__doc__ # Docstring des Attributs 'nicht aenderbar' 3935042698_v01.book Seite 84 Freitag, 14. Oktober 2005 3:31 15
Statische Methoden 85 2.12 Statische Methoden Statische Methoden einer Klasse können aufgerufen werden, ohne vorher ein Objekt der Klasse zu instanzieren. Eine Klasse mit statischen Methoden stellt man sich häufig als Toolbox vor. Sie enthält verschiedene Operationen, die thematisch zusammengehören, und bleibt immer im gleichen Zustand. Im folgenden Beispiel definieren wir eine Klasse STB (statistics toolbox) für statistische Berechnungen. Man ist hier nicht an Objekten (Instanzen) dieser Klasse interessiert, deshalb gibt es auch keine Attribute und keine __init__()-Prozedur, die Attribute ini- tialisiert. Beachten Sie: Die beiden Methodenköpfe enthalten in ihrer Para- meterliste kein self. Durch Aufrufe der Standardfunktion staticmethod() in den letzten beiden Zeilen werden statische Methoden generiert. Es ist kein Problem, dass es bereits eine Standardfunktion namens range() gibt. Statische Methoden werden im Format Klasse.Methode() aufgerufen. Hier einige Beispiele: class STB: def mean (s): # Mittelwert der Zahlen aus Sequenz s return float(sum(s))/len(s) def range (s): # Abstand zwischen groesstem und kleinsten # Element aus Sequenz s return max(s) - min(s) mean = staticmethod(mean) range = staticmethod(range) >>> STB.mean([2, 10, 6]) 6.0 >>> STB.range([2, 10, 6]) 8 3935042698_v01.book Seite 85 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 86 2.13 Iteratoren, iterierbare Klassen Ein Iterator ist ein Objekt, das nach und nach alle Elemente einer Kollektion liefert. Iteratoren gehören zu den klassischen Design-Patterns des berühm- ten Buchs von Erich Gamma u.a.1 Ein Alltagsbeispiel für einen Iterator ist die Sprechstundenhilfe im Vorzimmer einer Arztpraxis. Sie sorgt dafür, dass alle Patienten, die im Wartezimmer sitzen, nach und nach dem Doktor vor- geführt werden. Vom Arzt erhält sie gelegentlich den Auftrag "Der Nächste bitte!", sie wählt dann den nächsten Patienten aus und schickt ihn ins Be- handlungszimmer. Objekte der Standardtypen str, tuple, list, dict, set und frozenset sind iterier- bar. Zu ihnen gibt es vorgegebene Iteratoren, mit denen alle enthaltenen Ele- mente (Items) durchlaufen werden können. Implizit werden diese Iteratoren bei for-Schleifen (Iterationen) verwendet. Die Standardfunktion iter() liefert einen expliziten Iterator zu einer Kollektion. Sie können mit Python aber auch selbst Iteratoren mit besonderen Eigen- schaften definieren. Der formale Aufbau und die Arbeitsweise eines Itera- tors und eines iterierbaren Objekts sind durch ein Protokoll festgelegt. Es umfasst die folgenden Regeln: 1. Ein Iterator muss eine Methode namens next() besitzen. Wenn sie auf- gerufen wird, gibt sie das nächste Element der zugehörigen Kollektion zurück. Wenn bereits alle Elemente durchlaufen sind, ist der Iterator "er- schöpft" und erzeugt eine StopIteration-Exception. 2. Ein iterierbares Objekt (z.B. eine Sequenz) besitzt eine magische Metho- de mit dem Namen __iter__(). Sie liefert für den eigenen Datenbestand ein Iteratorobjekt. 3. Auch der Iterator selbst ist iterierbar und besitzt die Methode __iter__(). Ein Aufruf der Standardfunktion iter() wird vom Interpreter in einen Aufruf der __iter__()-Methode des betreffenden Objekts umgewandelt. Aus iter(collection) wird collection.__iter__(). Im folgenden Beispiel definieren 1. Gamma, E.; Helm, R.; Johnson, R.; Vlissides, J.: Design Patterns-Elements of Resuable Object-Oriented Software, Addison-Wesley, Reading, MA, 1995. 3935042698_v01.book Seite 86 Freitag, 14. Oktober 2005 3:31 15
Iteratoren, iterierbare Klassen 87 wir einen Iterator, der die Elemente einer Sequenz (String, Tupel oder Liste) nach und nach in sortierter Reihenfolge zurückgibt. Kommentar: #1: Die bei der Instanzierung übergebene Sequenz wird zunächst in eine Liste überführt, diese wird sortiert und dem Attribut seq zugewiesen. Es ent- hält nun die Items der übergebenen Sequenz in Form einer sortierten Liste. #2: Das Attribut index enthält während der Iteration den Index des zuletzt von next() zurückgegebenen Items. Es wird zu Beginn auf -1 gesetzt, weil die next()-Methode zuerst das index-Attribut inkrementiert. #3: Auch der Iterator ist iterierbar und gibt hier als Iterator sich selbst zu- rück. #4: Sofern das Ende der sortierten Liste noch nicht erreicht ist, wird das nächste Item zurückgegeben. #5: Andernfalls wird eine StopIteration-Exception ausgelöst. class SortedIterator(object): def __init__(self, sequence): self.seq = sorted(list(sequence)) #1 self.index = -1 #2 def __iter__(self): #3 return self def next(self): self.index += 1 if self.index < len(self.seq): return self.seq[self.index] #4 else: raise StopIteration #5 3935042698_v01.book Seite 87 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 88 Beispielaufrufe: Auch in einer for-Anweisung funktioniert der Iterator: Dictionaries sind iterierbar. Der Standarditerator eines Objekts vom Typ dict liefert alle Schlüssel (keys) des Dictionarys in einer Reihenfolge, deren Kon- struktionsprinzip nach außen nicht sichtbar ist. (Die Reihenfolge richtet sich nach internen Hash-Werten für die Schlüssel.) Wir bilden nun eine Unter- klasse von dict, in der wir den neuen Iterator SortedIterator verwenden. Die Schlüssel sollen in alphabetischer Reihenfolge durchlaufen werden. Dazu überschreiben wir die __iter__()-Methode: >>> i = SortedIterator("cab") >>> i.next() 'a' >>> i.next() 'b' >>> i.next() 'c' >>> i.next() Traceback (most recent call last): ...StopIteration >>> >>> for i in SortedIterator("cab"): print i, abc class SortedDict(dict): def __init__(self, d = {}): dict.__init__(self, d) def __iter__(self): return SortedIterator(self.keys()) 3935042698_v01.book Seite 88 Freitag, 14. Oktober 2005 3:31 15
Klassen für unveränderbare Objekte 89 Wir erzeugen nun im interaktiven Modus (Shell-Fenster) ein Dictionary und testen zunächst den Standarditerator des dict-Objekts: Die Schlüssel des Dictionarys erscheinen in unsortierter Reihenfolge. Nun testen wir die neue Klasse SortedDict: Sie sehen, dass die Schlüssel in alphabethischer Reihenfolge aufgelistet werden. 2.14 Klassen für unveränderbare Objekte Immer wenn ein neues Objekt einer Klasse instanziert wird, ruft der Inter- preter zunächst die Methode __new__() auf und erst dann __init__(). Die Methode __new__ () verwendet als erstes Argument einen Klassennamen, üblicherweise wählt man den Bezeichner cls. Die weiteren Argumente be- zeichnen Attributwerte für die Initialisierung. Sie gibt ein neues Objekt (In- stanz) der Klasse zurück und ist somit die eigentliche Konstruktormethode einer Klasse. Meist wird __new__() von der Basisklasse unverändert über- nommen. Will man jedoch eine Unterklasse von einem unveränderbaren (immutable) Standardtyp (str, tuple, int, float, complex) bilden und dabei die Unveränder- barkeit erhalten, muss man sie überschreiben. Das folgende Skript zeigt ein Beispiel. Die Klasse Upstring wird als Unterklasse des Standardtyps str de- finiert und stellt Zeichenketten aus Großbuchstaben dar. >>> d1 = {"sun": "Sonne", "house": "Haus", "rising": "aufsteigend"} >>> for key in d1: print key, sun rising house >>> d2 = SortedDict(d1) >>> for key in d2: print key, house rising sun 3935042698_v01.book Seite 89 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 90 Im Körper der Methode __new__() wird die (statische) Methode __new__() der Basisklasse str aufgerufen (#1). Sie erhält im ersten Argument einen (beliebigen) Klassennamen und im zweiten Argument einen String aus Großbuchstaben, der mit der String-Methode upper() aus dem Argument arg berechnet worden ist. Die Methode str.__new__() liefert ein String-Ob- jekt aus Großbuchstaben, das einfach weitergereicht wird. 2.15 Introspektion Python unterstützt Introspektion, das heißt die Erkundung der Klassenstruk- tur eines Programms. Der zentrale Mechanismus sind introspektive Attribu- te, die mit doppelten Unterstrichen beginnen und enden. Sie gestatten Ein- blicke in ihren internen Aufbau von Klassen und Objekten (Tabelle 2.2). Beispielsweise kann man den Docstring einer Klasse abfragen oder den Na- men der Klasse eines Objekts erkunden: class Upstring(str): "String aus Grossbuchstaben" def __new__(cls, arg=""): return str.__new__(cls, arg.upper()) #1 >>> a = Upstring("klein") >>> a 'KLEIN' >>> print list.__doc__ # Docstring der Klasse list list() -> new list list(sequence) -> new list initialized from sequence's items >>>a=1 >>> a.__class__.__name__ # Klassenname des Objektes 1 'int' 3935042698_v01.book Seite 90 Freitag, 14. Oktober 2005 3:31 15
Introspektion 91 Python bietet auch einige Funktionen zur Introspektion. Tabelle 2.3 gibt ei- nen kleinen Überblick. Zu beachten ist, dass Python nur Namen kennt und zwischen Attributnamen und Methodennamen nicht differenziert. In der Re- deweise von Python bilden Attribut- und Methodennamen den Namensraum eines Objekts. Bei Fehlermeldungen werden Methoden auch als Attribute bezeichnet: Attribut Erklärung __class__ Name der Klasse, zu der das Objekt gehört. Falls das Objekt selbst eine Klasse ist, enthält __class__ die Metaklasse. __bases__ Tupel mit Basisklassen, von denen die Klasse abgelei- tet ist __dict__ Dictionary, in dem allen Namen des Objekts bzw. der Klasse die zugehörigen Objekte zugeordnet sind __doc__ Docstring __name__ String mit dem Namen des Objekts bzw. der Klasse Tabelle 2.2: Einige introspektive Attribute >>> hasattr(str, "__len__") # __len__() ist Methode True >>> int.__len__() Traceback (most recent call last): File "<pyshell#35>", line 1, in -toplevel- int.__len__() AttributeError: type object 'int' has no attribute '__len__' 3935042698_v01.book Seite 91 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 92 Introspektive Attribute dienen nicht allein der Erkenntnisgewinnung, son- dern können auch algorithmisch genutzt werden. Das folgende Skript imp- lementiert Konstanten, die weder geändert noch gelöscht werden können. Hier wird das Attribut __dict__ genutzt. Die Idee ist folgende: In der Definition der Klasse Const werden die "magi- schen" Methoden __setattr__() und __delattr__() überschrieben. Wenn man versucht, einem Const-Objekt ein neues Attribut hinzuzufügen, wird die Me- thode __setattr__() aufgerufen. Falls dieses Attribut bereits existiert, befindet Funktion Erklärung dir([object]) Liefert eine Liste mit Strings, die die Namen der Attribute und Methoden des Objekts enthalten. hasattr(object, name) Liefert den Wert Tr u e , falls das Objekt object das Attribut name besitzt, ansonsten False. Dabei ist object eine Referenz auf ein Objekt und name ein String. id(object) Liefert die Identität (natürliche Zahl) eines Objekts. issinstance(object, class) Liefert den Wert Tr u e , wenn object eine Instanz von class ist, ansonsten False. issubclass(C, B) Liefert den Wert Tr u e , wenn Klasse C eine Unter- klasse von B ist, ansonsten False. Tabelle 2.3: Standardfunktionen zur Introspektion von Klassen und Objekten class Const(object): def __setattr__(self, name, wert): if name in self.__dict__: raise Exception, \ "Can't rebind %s" % (name,) #1 else: self.__dict__[name] = wert #2 def __delattr__(self, name): #3 if name in self.__dict__: raise Exception, \ "Can't delete %s" % (name,) #4 else: raise NameError, name const = Const() #5 3935042698_v01.book Seite 92 Freitag, 14. Oktober 2005 3:31 15
Metaklassen 93 sich sein Name in der Schlüsselmenge des Dictionarys __dict__. In diesem Fall wird eine Exception ausgelöst (#1). Nur wenn sein Name noch nicht exis- tiert, kann es erzeugt und ihm ein Wert zugewiesen werden (#2). Beim Ver- such, ein Attribut des Const-Objekts zu löschen, gibt es zwei Fehlermöglich- keiten: Entweder der Attributname existiert in __dict__. Dann gibt es eine Exception mit dem Hinweis, dass das Attribut nicht gelöscht werden darf. Oder aber der Attributname existiert nicht. Dann kann das Attribut auch nicht gelöscht werden und das Objekt erzeugt einen NameError (#4). In Zeile #5 wird ein Objekt namens const der Klasse Const erzeugt. Das Objekt const lässt sich zur Definition von Konstanten verwenden, die weder geändert noch gelöscht werden können. Hier einige Testaufrufe in der Python-Shell: 2.16 Metaklassen Klassen sind Muster für Objekte. Eine Klasse stellt also einen Mechanismus zur Erzeugung von Objekten bereit. Aber auch eine Klasse wird bei Python als Objekt betrachtet. So hat z.B. jede Klasse wie jedes andere Objekt eine Identität und Klassen können wie andere Objekte verarbeitet werden: >>> const.a = 42 >>> const.a 42 >>> const.a = 43 # Versuch einer Aenderung Traceback (most recent call last): ... Exception: Can't rebind a >>> del const.a # Loeschversuch Traceback (most recent call last): ... Exception: Can't delete a >>> id(int) # Identitaet der Klasse int 504883456 >>> print int <type 'int'> >>> isinstance(1, int) # Ist 1 Instanz der Klasse int? True >>> 3935042698_v01.book Seite 93 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 94 Eine Metaklasse ist ein Muster, nach dem Klassen erzeugt werden. Wenn man sich eine Klasse als Bauplan für Objekte vorstellt, dann ist eine Meta- klasse ein Bauplan für Baupläne. Oder anders ausgedrückt: Die Instanzen einer Metaklasse sind Klassen. Bei Python ist eine Metaklasse von vorn- herein vorgegeben. Sie heißt type. Sie ist die Metaklasse aller Standardtypen (int, float, ...). Meistens verwendet man den Namen type wie eine Funktion, die prüfen soll, zu welchem Typ bzw. zu welcher Klasse ein Objekt gehört. Tatsächlich ist type() eine Factory für Klassen. Der Aufruf type(1) liefert keine bloße Bezeichnung der Klasse des Objekts 1, sondern die Klasse selbst. Welche Art von Information ist in einer Metaklasse enthalten? Sie be- schreibt allgemeine Aspekte, wie Klassen gebildet werden. Ein solches grundsätzliches Prinzip, das sich nicht auf einzelne Objekte, sondern auf Klassen bezieht, wurde in diesem Kapitel bereits angesprochen: die Ermitt- lung der Method Resolution Order (MRO), d.h. die Reihenfolge, in der der Interpreter nach geerbten Methoden für eine Klasse sucht. Die MRO ist ein Attribut einer Klasse, nicht einer Klasseninstanz. Und das Verfahren, nach dem diese Reihenfolge ermittelt wird, ist eine Operation der Metaklasse type. Wenn man mit diesem Verfahren nicht einverstanden ist und ein ande- res verwenden möchte, muss man eine neue Metaklasse definieren. Wenn eine class-Anweisung ausgeführt wird, ermittelt der Interpreter zuerst eine geeignete Metaklasse M und startet einen Aufruf des folgenden For- mats: >>> type(1) <type 'int'> >>> Ganzzahl = type(1) # Klasse des Objektes 1 >>> a = Ganzzahl(15.9) # Ganzzahl-Instanz >>> a 15 M(Name, Basis, Dict) 3935042698_v01.book Seite 94 Freitag, 14. Oktober 2005 3:31 15
Metaklassen 95 Dabei ist Name ein String mit der Bezeichnung der neuen Klasse (Inhalt des Attributs __name__ des Klassenobjekts), Basis ist ein (eventuell leeres) Tupel mit den Namen der Basisklassen, von denen die neue Klasse abgelei- tet wird, und Dict ist ein Dictionary, das die in der class-Anweisung defi- nierten Namen enthält. Im folgenden Beispiel definieren wir gewissermaßen "von Hand" eine triviale Klasse durch einen Aufruf der Metaklasse type: Die Metaklasse type ist auch eine Klasse. Folglich kann man von ihr Klassen ableiten und auf diese Weise neue Metaklassen schaffen. Die folgende Me- taklasse Ve r b o s e Ty p e ist eine "gesprächige" Erweiterung der Metaklasse type. Sie verursacht eine Meldung auf dem Bildschirm, wenn eine neue Klasse ihres Typs erzeugt worden ist: Wie stellt der Interpreter fest, welche Metaklasse er zur Erstellung einer neu- en Klasse im Rahmen eines class-Statements heranziehen soll? Wenn man keine besonderen Vorkehrungen trifft, wird die vorgegebene Standard-Me- taklasse type verwendet. Bei der Ausführung der class-Anweisung passiert dann nichts Besonderes. >>> C = type("Class", (object,), {}) >>> C <class '__main__.Class'> >>> C.__name__ 'Class' >>>a=C() >>> type(a) <class '__main__.Class'> >>> class VerboseType(type): def __new__(cls, name, bases, dct): print "Ich erzeuge neue Klasse", name print "Basisklassen:", for b in bases: print b, return type.__new__(cls, name, bases, dct) >>> class C(object): pass >>> 3935042698_v01.book Seite 95 Freitag, 14. Oktober 2005 3:31 15
2 -- Objektorientierte Programmierung 96 Soll die neue Metaklasse VerboseType zum Einsatz kommen, so kann man sie im Rahmen einer class-Anweisung dem Klassenattribut __metaclass__ zuweisen. Damit wird der Interpreter gezwungen, nicht type, sondern die spezifizierte Metaklasse zu verwenden. Es bleibt die Frage, welchen Nutzen Metaklassen in realen Projekten haben. Tim Peters (Python Software Foundation) meint dazu: "Metaclasses are deeper magic, than 99% of users should ever worry about. If you wonder whether you need them, you don't."1 David Mertz und Michele Simionato beschreiben Anwendungen aus den Bereichen aspektorientierte Program- mierung und deklarative Mini-Sprachen. 2.17 Literatur Balzert, Heide: Lehrbuch der Objektmodellierung, Heidelberg Berlin 2004 Barrett, Kim; Cassels, Bob; Haahr, Paul; Moon, David A.; Playford, Keith; Withington, P. Tucker: A Monotonic Superclass Linearization for Dylan. OOPSLA 1996 O'Brian, Patrick K.: Guide to Python introspection. IBM developerWorks Dezember 2002, http://www-106.ibm.com/developerworks/library/l-pyint. html Gamma, E.; Helm, R.; Johnson, R.; Vlissides, J.: Design Patterns-Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1995. >>> class C(object): __metaclass__ = VerboseType Ich erzeuge neue Klasse C Basisklassen: <type 'object'> >>> 1. Posting vom 22.12.2002 in der Usenet-Newsgroup comp.lang-python 3935042698_v01.book Seite 96 Freitag, 14. Oktober 2005 3:31 15
Literatur 97 Martelli, Alex; Martelli Ravenscroft, Anna; Ascher, David (Hrsg.): Python Cookbook, 2. Auflage, Beijing, Cambridge Farnham Köln Paris Sebastopol Taipei Tokio (O'Reilly) 2005. Mertz, David; Simionato, Michele: Metaclass programming in Python, IBM developerWorks Februar 2003, http://www-128.ibm.com/developerworks/ linux/library/l-pymeta.html Mertz, David: Charming Python: Create declarative mini-languages, IBM developerWorks, Februar 2003, http://www-106.ibm.com/developerworks/ library/l-cpdec.html van Rossum, Guido: Unifying types and classes in Python 2.2, Python Soft- ware Foundation 2002, http://www.python.org/2.2.1/descrintro.html Weigend, Michael: Objektorientierte Programmierung mit Python, 2. Auf- lage, Bonn (mitp) 2005 3935042698_v01.book Seite 97 Freitag, 14. Oktober 2005 3:31 15
3935042698_v01.book Seite 98 Freitag, 14. Oktober 2005 3:31 15
99 3 Applets bauen mit gDesklets Von Martin Grimme Desktop-Applets sind Mini-Programme, die auf der Desktop-Oberfläche lau- fen. Dieses Kapitel stellt gDesklets vor, ein hauptsächlich in Python entwi- ckeltes, sehr mächtiges Framework für Desktop-Applets auf Unix- und Linux- Desktops. Bei Applets mögen Sie vielleicht zuerst an Mini-Programme denken, die auf manchen Internetseiten eingebettet sind und im Webbrowser laufen. In den letzten Jahren haben jedoch Applets, die direkt auf dem Desktop laufen, ei- nen regelrechten Boom erlebt. Mit gDesklets können Sie solche Applets sehr einfach selber bauen. Neben minimalen Python-Kenntnissen benötigen Sie nur ein wenig XML-Erfahrung. Den Einsatzmöglichkeiten sind keine Gren- zen gesetzt, ganz gleich ob Sie sich für Nachrichtenschlagzeilen, Wetterbe- richte, Börsenticker, Spiele oder nützliche Hilfsprogramme interessieren. 3.1 Erste Schritte Wenn Sie gDesklets bereits erfolgreich auf Ihrem System einsetzen, können Sie diesen Abschnitt überspringen. Für alle anderen folgt hier eine kurze Anleitung, wie gDesklets installiert und benutzt wird. Für viele Distributionen gibt es bereits vorkompilierte Pakete. In diesem Fall müssen Sie nur das für Ihre Distribution passende Paket finden und unter Berücksichtigung der Abhängigkeiten von anderen Paketen installieren. Falls kein vorkompiliertes Paket für Ihre Distribution zur Verfügung steht oder es nicht aktuell genug ist, müssen Sie gDesklets aus dem Quellcode übersetzen.1 1. Sie finden die jeweils aktuelle Version von gDesklets auf http://www.gdesklets.org zum Herunterladen. 3935042698_v01.book Seite 99 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 100 Die Installation erfolgt dann durch die zum Übersetzen und Installieren üb- lichen drei Kommandos: Genauere Details zur Installation entnehmen Sie bitte der gDesklets beige- fügten README-Datei. Nach erfolgreicher Installation kann gDesklets mit das erste Mal gestartet werden. Um es zu beenden, schreiben Sie oder schließen Sie es wie in der README-Datei beschrieben über die gra- fische Benutzungsoberfläche. Wenn Sie ein Desklet heruntergeladen oder selbst geschrieben haben, kön- nen Sie es mit dem Kommando starten, wobei Sie datei.display durch die Datei des jeweiligen Desklets er- setzen. Es gibt auch eine grafische Oberfläche, die gDesklets-Shell, um Des- klets zu starten. Diese wird mit aufgerufen. Informationen zum Umgang mit der Shell finden Sie in der README-Datei. $ ./configure $ make $ su -c "make install" $ gdesklets start $ gdesklets stop $ gdesklets open datei.display $ gdesklets shell 3935042698_v01.book Seite 100 Freitag, 14. Oktober 2005 3:31 15
Das Framework von gDesklets 101 3.2 Das Framework von gDesklets Bei gDesklets gibt es einen Daemon-Prozess, der im Hintergrund läuft und alle Applets ausführt. Frontends wie das gdesklets-Kommando oder die gDesklets-Shell steuern den Daemon. Das System stellt für Desktop-Applets (kurz: Desklets) ein Framework wie in Abbildung 3.1 zur Verfügung. Abb. 3.1: Das gDesklets-Framework 1. Das Aussehen der Desklets wird durch eine XML-Sprache definiert, die speziell auf das Erstellen von Applets zugeschnitten ist. Dabei handelt es sich um die ADL (Applet Description Language, Applet-Beschrei- bungssprache), die sich durch Einfachheit und Flexibilität auszeichnet. Die wichtigsten Bestandteile dieser Sprache werden in Kapitel 3.3 be- handelt. 2. Mit XML allein sind natürlich nur statische Desklets möglich. Eingebet- tete Python-Skripte (Inline-Scripts) jedoch können alle mit der ADL er- zeugten Elemente direkt ansprechen und zur Laufzeit modifizieren. Ein- gebettete Skripte werden in Kapitel 3.5 beschrieben. 3935042698_v01.book Seite 101 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 102 3. Damit der Benutzer die Applets konfigurieren kann und damit die Kon- figuration zwischen Sitzungen gespeichert wird, bietet gDesklets spezi- elle Elemente an, die in Kapitel 3.6 behandelt werden. 4. Damit auch die Systemsicherheit nicht zu kurz kommt, dürfen in den Skripten keine weiteren Module importiert werden. Sie laufen nämlich in einer abgeschirmten Umgebung (Sandbox) und haben somit keinen Zugriff auf das System. Um die Möglichkeiten dennoch nicht einzu- schränken, können, wie in Kapitel 3.7 beschrieben, so genannte Controls geladen werden. Das sind Komponenten, die den kontrollierten Zugriff auf die Welt außerhalb der Sandbox ermöglichen. Wie Sie eigene Con- trols in Python schreiben, erklärt Kapitel 3.8. 3.3 Die Applet-Beschreibungssprache In diesem Abschnitt lernen Sie die Applet Description Language von gDes- klets, kurz ADL, kennen. Damit können bereits einfache nicht-interaktive Applets realisiert werden. 3.3.1 Hello-World-Beispiel Für nahezu jede Programmiersprache existiert das obligatorische Hello- World-Beispiel. Für gDesklets sieht das so aus: Speichern Sie das Beispiel als hello.display ab und starten Sie es mit <?xml version="1.0" encoding="UTF-8"?> <display window-flags="sticky, above" bg-color="white"> <label font="Sans Serif 20" color="navy" value="Hello gDesklets World!"/> </display> $ gdesklets open hello.display 3935042698_v01.book Seite 102 Freitag, 14. Oktober 2005 3:31 15
Die Applet-Beschreibungssprache 103 Die erste Zeile ist optional, wird aber für XML-Dokumente empfohlen, da- mit der XML-Parser weiß, auf welche Zeichenkodierung er sich bei dem Dokument einstellen muss. Hello World verwendet bereits zwei ADL-Elemente: <display> für das Applet-Fenster und <label> für den darzustellenden Text. Das Wurzel- element eines Desklets muss stets <display> sein. Weil Elemente wie <dis- play> Kind-Elemente enthalten können, werden sie Container-Elemente ge- nannt. 3.3.2 Attribute und Action-Handler Wie im Hello-World-Beispiel zu sehen, werden alle ADL-Elemente durch Attribute angepasst. Es gibt einige allgemeine Attribute, die jedes ADL-Ele- ment kennt. Tabelle 3.1 führt diese Attribute auf. Attribut Beschreibung anchor Die Positionierungsverankerung des Elements (siehe Kapi- tel 3.4.2) Voreinstellung: nw id Der identifizierende Name für das Element, damit es in Skripten angesprochen werden kann. Der Name muss ein syntaktisch korrekter Python-Variablenname sein, damit er in Python-Skripten verwendbar ist. relative-to Das Bezugselement und die Richtung für die relative Posi- tionierung (siehe Kapitel 3.4.3) visible Der Sichtbarkeitszustand des Elements. Dieser boolesche Wert (Tr u e oder False) legt fest, ob das Element momentan sichtbar sein soll. Nicht-sichtbare Elemente beanspruchen keinen Platz für sich und reagieren auch nicht auf Benutzer- eingaben. Voreinstellung: Tr u e (sichtbar) x Die x-Koordinate Voreinstellung: 0 y Die y-Koordinate Voreinstellung: 0 Tabelle 3.1: Allgemeine Attribute 3935042698_v01.book Seite 103 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 104 Für interaktive Applets ist es wichtig, auf Benutzereingaben zu reagieren. Dazu werden in der ADL so genannte Action-Handler verwendet. Ein Ac- tion-Handler ist wie ein Attribut, jedoch mit dem Unterschied, dass anstelle eines Werts Python-Code eingebettet wird. Dieser wird ausgeführt, sobald das Ereignis eintritt. Jeder Action-Handler erzeugt ein Event-Objekt (siehe Kapitel 3.5.1) mit Attributen, die genauere Angaben zu dem Ereignis ma- chen. Die in Tabelle 3.2 angegebenen allgemeinen Action-Handler werden von allen ADL-Elementen unterstützt. width Die Breite des Elements. Wenn die Breite nicht explizit vor- gegeben wird, passt sie sich dynamisch an. Voreinstellung: undefiniert (passt sich dynamisch an) height Die Höhe des Elements. Wenn die Höhe nicht explizit vor- gegeben wird, passt sie sich dynamisch an. Voreinstellung: undefiniert (passt sich dynamisch an) Handler Event-Objekt Beschreibung on-click button xy Reagiert auf das Klicken einer Maus- taste on-doubleclick button xy Reagiert auf das Doppelklicken einer Maustaste on-enter Reagiert auf das Betreten eines Ele- ments mit der Maus on-file-drop files Reagiert auf Drag&Drop-Operationen mit Dateien on-key-press key Reagiert auf das Niederdrücken einer Taste on-key-release key Reagiert auf das Loslassen einer Taste on-leave Reagiert auf das Verlassen eines Ele- ments mit der Maus Tabelle 3.2: Allgemeine Action-Handler Attribut Beschreibung Tabelle 3.1: Allgemeine Attribute (Forts.) 3935042698_v01.book Seite 104 Freitag, 14. Oktober 2005 3:31 15
Die Applet-Beschreibungssprache 105 3.3.3 Container-Elemente Container-Elemente können ihrerseits Elemente enthalten. Auf diese Weise lassen sich Elemente beliebig tief verschachteln. Einen Container haben Sie bereits im Hello-World-Beispiel kennen gelernt: <display>. Daneben bietet gDesklets eine Reihe weiterer Container an. <array> Das Array kann dynamisch Elemente erzeugen und löschen. Das Kind-Ele- ment wird je nach Länge des Arrays entsprechend oft vervielfacht. Mit Skripten kann jedes Duplikat unabhängig von den anderen parametrisiert werden. Tabelle 3.3 listet die Attribute auf. on-link-drop links Reagiert auf Drag&Drop-Operationen mit Webbrowser-Links on-motion xy Reagiert auf Bewegen der Maus on-press button xy Reagiert auf das Niederdrücken einer Maustaste on-release button xy Reagiert auf das Loslassen einer Maustaste on-scroll direction Reagiert auf Rollen des Mausrads Attribut Beschreibung layout Die Anordnung der Kind-Elemente, falls Sie diese nicht selbst übernehmen wollen. Zur Verfügung stehen horizontal und vertical für waagerechte bzw. senkrechte Anordnung. length Die Länge des Arrays, d.h., wie oft das Kind-Element erscheinen soll Voreinstellung: 0 Tabelle 3.3: <array>-Attribute Handler Event-Objekt Beschreibung Tabelle 3.2: Allgemeine Action-Handler (Forts.) 3935042698_v01.book Seite 105 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 106 <display> Dieser Container repräsentiert das Fenster des Applets und muss als Wurzel- element in jedem Desklet vorhanden sein. Er verhält sich wie <group>, kennt aber noch die in Tabelle 3.4 angegebenen zusätzlichen Attribute. <frame> Der Rahmen dient zum grafischen Einrahmen von Elementen. Er kann ent- weder einfarbig oder aber aus insgesamt acht Grafikblöcken zusammenge- setzt sein. Dieser Container akzeptiert nur ein einzelnes Kind-Element. Wenn Sie mehrere Elemente umrahmen wollen, dann müssen Sie diese zu- vor in eine <group> setzen. Tabelle 3.5 listet die Attribute auf. Attribut Beschreibung desktop-borders Die Einstellung für Desktop-Ränder. Einige Fenster- Manager unterstützen die Angabe von Rändern, über die sich maximierte Fenster nicht erstrecken können. icon Das Icon für das Applet-Fenster, falls dieses einen Rahmen hat shape Fenster müssen nicht immer rechteckig sein. Mit diesem Attribut können Sie eine transparente Bild- datei als Maske angeben. Transparente Stellen im Bild werden dann vom Fenster weggeschnitten. title Der Fenstertitel, falls das Fenster einen Rahmen hat window-flags Durch Kommas getrennte Fensterattribute: above (über allen anderen Fenstern), below (unter allen anderen Fenstern), sticky (auf allen virtuellen Desk- tops), decorated (mit Fensterrahmen). Voreinstellung: decorated Tabelle 3.4: <display>-Attribute 3935042698_v01.book Seite 106 Freitag, 14. Oktober 2005 3:31 15
Die Applet-Beschreibungssprache 107 <gauge> Mit diesem Container können Sie Balken in der Art von Fortschrittsbalken erzeugen. <gauge> akzeptiert genau ein Kind-Element, von welchem dann nur ein konfigurierbarer Prozentsatz sichtbar ist. Sie können horizontale und vertikale Balken erzeugen. Tabelle 3.6 listet die Attribute auf. <group> Dieser Container gruppiert Elemente, damit sie wie ein Ganzes behandelt werden können. <group> darf beliebig viele Kind-Elemente enthalten, und kann darüber hinaus mit einer Hintergrundfarbe oder -grafik versehen wer- den. Die Attribute sind in Tabelle 3.7 aufgeführt. Attribut Beschreibung border-uris Durch Kommas getrennte Liste von Pfaden für die acht Rahmenelemente links, oben, rechts, unten, links oben, rechts oben, rechts unten und links unten. border-width Durch Kommas getrennte Liste für die Rahmendicke links, rechts, oben und unten. Voreinstellung: 2, 2, 2, 2 color Die Farbe des Rahmens (siehe auch Kapitel 3.3.5) Voreinstellung: black Tabelle 3.5: <frame>-Attribute Attribut Beschreibung fill Der sichtbare prozentuale Anteil des Kind-Elements als Wert zwischen 0 und 100 Voreinstellung: 100 orientation Die Ausrichtung des Balkens; entweder horizontal oder vertical Voreinstellung: horizontal Tabelle 3.6: <gauge>-Attribute 3935042698_v01.book Seite 107 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 108 3.3.4 Sonstige Elemente Die nun aufgeführten Elemente sind allesamt keine Container und dürfen keine Kind-Elemente enthalten. <canvas> In dieses Element kann mit SVG1 gezeichnet werden. Mittels SVG-Skrip- ting über das dom-Attribut sind auch Animationen möglich. Tabelle 3.8 zeigt die Attribute. Attribut Beschreibung bg-color Die Hintergrundfarbe. Für Transparenz kann ein Alpha-Kanal verwendet werden (siehe auch Kapitel 3.3.5). Voreinstellung: #00000000 bg-uri Der Pfad für die Hintergrundgrafik, die über den Hin- tergrund gekachelt wird. Dieses Attribut über- schreibt bg-color. Tabelle 3.7: <group>-Attribute 1. SVG ist ein offenes XML-Format für Vektorgrafiken. Auf http://www.w3.org/Graphics/ SVG/ finden Sie weitere Informationen dazu. Attribut Beschreibung dom Die Referenz auf die Mini-DOM-Repräsentation der SVG-Grafik. Dieses Attribut ist nur lesbar. graphics Der SVG-Code für die Grafik uri Der Pfad zur anzuzeigenden SVG-Grafik. Dieses Attribut überschreibt graphics. Tabelle 3.8: <canvas>-Attribute 3935042698_v01.book Seite 108 Freitag, 14. Oktober 2005 3:31 15
Die Applet-Beschreibungssprache 109 <entry> In dieses Element kann Text eingegeben werden, der sich auslesen lässt. Die in Tabelle 3.9 aufgeführten Attribute stehen zur Verfügung. <image> Dieses Element zeigt Bilder an. Die meisten gebräuchlichen Pixelformate werden unterstützt und die Bilder können je nach Bedarf skaliert werden. Tabelle 3.10 zeigt die Attribute. Attribut Beschreibung color Die Textfarbe (siehe auch Kapitel 3.3.5) font Die Schriftart (siehe auch Kapitel 3.3.6) value Der eingegebene Text kann hier ausgelesen werden. Tabelle 3.9: <entry>-Attribute Attribut Beschreibung opacity Die Deckkraft als Wert zwischen 0.0 (komplett durchsichtig) und 1.0 (komplett deckend) Voreinstellung: 1.0 scale Der Skalierungsfaktor, falls width und height nicht gesetzt sind Voreinstellung: 1.0 uri Der Pfad zur anzuzeigenden Bilddatei Tabelle 3.10: <image>-Attribute 3935042698_v01.book Seite 109 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 110 <label> Dieses Element stellt Text dar, der auch mehrzeilig sein darf und manuell oder automatisch umgebrochen werden kann. Die Attribute sind in Tabelle 3.11 beschrieben. <plotter> Der Funktionsplotter kann Graphen zeichnen. Dazu werden die Werte ein- zeln Wert für Wert an das value-Attribut übergeben. Das Ergebnis ist eine Kurve, die je nach Wertebereich automatisch skaliert. Tabelle 3.12 zeigt die Attribute. Attribut Beschreibung color Die Textfarbe. Durch Verwenden des Alpha-Kanals kann Transparenz erzielt werden (siehe auch Kapitel 3.3.5). font Die Schriftart (siehe auch Kapitel 3.3.6) value Der darzustellende Text wrap-at Die Breite, ab der Text umgebrochen wird Voreinstellung: undefiniert (kein Umbruch) Tabelle 3.11: <label>-Attribute Attribut Beschreibung bg-color Die Hintergrundfarbe (siehe auch Kapitel 3.3.5) color Die Zeichenfarbe scale-bidir Unterstützung für negative Werte Voreinstellung: False scale-holdmax Skaliert nicht wieder herunter, nachdem ein Wert größer als 100 aus der Anzeige verschwunden ist. Voreinstellung: False size Die Anzahl der sichtbaren Werte Voreinstellung: 50 Tabelle 3.12: <plotter>-Attribute 3935042698_v01.book Seite 110 Freitag, 14. Oktober 2005 3:31 15
Die Applet-Beschreibungssprache 111 3.3.5 Farbwerte Farbattribute können in gDesklets ähnlich wie in HTML entweder über vor- definierte Farbnamen1 oder über RGB-Werte gesetzt werden. Die meisten Farbattribute erlauben auch RGBA-Angaben für Transparenzeffekte. Ein RGB-Wert hat den Aufbau #RRGGBB, wobei RR den hexadezimalen Rotanteil, GG den hexadezimalen Grünanteil und BB den hexadezimalen Blauanteil angeben. Beispiele sind: 1. #FFFFFF (Weiß) 2. #000000 (Schwarz) 3. #FF0000 (Rot) 4. #FFFF00 (Gelb) 5. #FFa500 (Orange) Bei RGBA-Werten kommt ein weiterer Kanal, der Alphakanal, hinzu. Diese Werte haben den Aufbau #RRGGBBAA, wobei der Alpha-Kanal AA die Deckkraft als hexadezimalen Wert zwischen 00 (durchsichtig) und FF (de- ckend) angibt. 3.3.6 Schriften Schriftarten werden in gDesklets als angegeben. Die Stilangabe ist optional und darf fehlen. Die Größe kann eine Maßeinheit enthalten (siehe dazu Kapitel 3.4.1). value Der nächste zu plottende Wert. Der Wert sollte zwi- schen 0 und 100 liegen, aber bei größeren Werten skaliert der Graph entsprechend. 1. Das Kommando showrgb listet die vordefinierten Farbnamen auf. <Schriftfamilie> <Stil> <Größe> Attribut Beschreibung Tabelle 3.12: <plotter>-Attribute (Forts.) 3935042698_v01.book Seite 111 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 112 Beispiele für gültige Schriftangaben: 1. Serif 20pt 2. Sans Serif bold 12cm 3. Monospace italic 3 3.3.7 Pfade und URLs Wann immer Dateien referenziert werden, können sowohl lokale Dateisys- tempfade als auch HTTP-URLs angegeben werden. Relative Pfadangaben werden relativ zum Pfad der .display-Datei aufgelöst. Beispiele: 1. /usr/share/pixmaps/gdesklets.png 2. http://www.mydomain.org/image.png 3. gfx/gdesklets.png 4. ../gfx/image.png Es ist auch möglich, die .display-Datei und sämtliche Ressourcen von einem entfernten Server auszuführen. Controls können aus Gründen der Sicherheit aber nur vom lokalen System aus geladen werden. 3.4 Layout Dieser Abschnitt stellt die Layoutmöglichkeiten von gDesklets vor, die in Abbildung 3.2 zu sehen sind. gDesklets vereint prozentuale und einheiten- basierte Positionierung (Abbildung 3.2 oben und Mitte) für auflösungsunab- hängige Layouts mit Positionsverankerung (Abbildung 3.2 links unten) und relativer Positionierung (Abbildung 3.2 rechts unten) und bietet so ein Höchstmaß an Flexibilität. 3935042698_v01.book Seite 112 Freitag, 14. Oktober 2005 3:31 15
Layout 113 Abb. 3.2: Beispiele für die in diesem Abschnitt vorgestellten Layout- möglichkeiten von gDesklets 3.4.1 Koordinateneinheiten Neben Pixelkoordinaten werden auch auflösungsunabhängige Einheiten un- terstützt. Dazu wird die Einheit wie in den oberen vier Beispielen in Abbil- dung 3.2 als Suffix an den Zahlenwert angehängt. gDesklets kann mit den folgenden Einheiten umgehen: 1. cm: Zentimeter 2. in: Zoll bzw. Inch 3. pt: DTP Punkt (1/72 Zoll) 4. %: ein prozentualer Anteil der Größe des Eltern-Containers 3935042698_v01.book Seite 113 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 114 Wenn keine Einheit angegeben ist, werden automatisch Pixelkoordinaten verwendet. Aufgrund verschiedener Auflösungen und physikalischer Bildschirmgrößen variiert das Verhältnis von Pixel zu den anderen Maßeinheiten von System zu System. Sie sollten Maßeinheiten deshalb nur mit Bedacht mit Pixelan- gaben mischen. Bei prozentualen Angaben passen sich die Elemente dynamisch an, sobald sich die Größe des Eltern-Containers verändert. Container ohne explizite Größenangabe werden von ihren Kind-Elementen aufgespannt, d.h., sie sind gerade so groß, wie die Kind-Elemente es erfor- dern. Beachten Sie jedoch, dass Kind-Elemente mit prozentualen Angaben per Definition nicht zum Aufspannen beitragen können. Ein Container ohne feste Größe, der nur Kind-Elemente mit prozentualen Angaben enthält, kol- labiert zu einem Punkt, so dass nichts mehr sichtbar ist. 3.4.2 Die Positionierungsverankerung Für das Positionieren eines Elements spielt die Positionsverankerung eine Rolle. Diese gibt an, welche Stelle des Elements auf den angegebenen Ko- ordinaten zu liegen kommt. Im Normalfall ist das die linke obere Ecke. Mit dem anchor-Attribut kann die Verankerung aber auch auf eine andere Stelle des Elements gelegt werden, um das Element anders auszurichten, wie das Beispiel unten links in Abbildung 3.2 zeigt. Die möglichen Werte für anchor sind: 1. nw -- die linke obere Ecke (Nordwest) 2. n -- die Mitte der oberen Kante (Nord) 3. ne -- die rechte obere Ecke (Nordost) 4. e -- die Mitte der rechten Kante (Ost) 5. se -- die rechte untere Ecke (Südost) 6. s -- die Mitte der unteren Kante (Süd) 7. sw -- die linke untere Ecke (Südwest) 8. w -- die Mitte der linken Kante (West) 9. center -- der Mittelpunkt des Elements 3935042698_v01.book Seite 114 Freitag, 14. Oktober 2005 3:31 15
Layout 115 Abbildung 3.3 zeigt die Auswirkungen der verschiedenen Positionsveranke- rungen. Abb. 3.3: Die Auswirkungen verschiedener anchor-Werte für x="50%", y="50%" 3.4.3 Relative Positionierung gDesklets kann Elemente auch so anordnen, dass sie relativ zueinander lie- gen, z.B. einen Text unter eine Grafik setzen. Dazu dient das relative-to- Attribut, welches von allen Elementen unterstützt wird. Sie müssen ledig- lich angeben, auf welches Element Sie sich beziehen wollen und ob Sie rechts (x), darunter (y) oder rechts darunter (xy) positionieren wollen. Die x- und y-Koordinaten werden dann relativ zur jeweiligen Position berechnet, wie im Beispiel rechts unten in Abbildung 3.2 zu sehen ist. Beispiel: <display> <image id="logo" uri="gfx/logo.png"/> <label relative-to="logo, y" value="Das Logo"/> </display> 3935042698_v01.book Seite 115 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 116 3.5 Eingebettete Skripte Bisher wurden nur statische Applets betrachtet, also solche, die ihr Ausse- hen nicht verändern. Mit Python-Skripten können Sie die Elemente jedoch zur Laufzeit beliebig manipulieren. 3.5.1 Skripte im Action-Handler In jedem Action-Handler können Sie mit Python-Code die Elemente modi- fizieren. Die self-Referenz verweist dabei auf das Element des Action- Handlers, so dass Attribute direkt verändert werden können. Sie können auf diese Weise alle Attribute des Elements ansprechen. Beachten Sie jedoch, dass das Minuszeichen (-) in Python bereits reserviert ist und deshalb nicht in Bezeichnern und somit auch nicht in Attributnamen auftauchen darf. Um Attribute mit einem Minuszeichen im Namen, wie z.B. bg-color, anzusprechen, müssen Sie das Minus durch einen Unterstrich er- setzen, also anstelle von schreiben. Jeder Action-Handler erzeugt ein Event-Objekt mit näheren Informationen über das eingetretene Ereignis. Auf dieses Objekt können Sie über das event-Attribut zugreifen. Das Event-Objekt selbst enthält je nach Ereignis eine Reihe von Attributen (siehe auch Kapitel 3.3). <label value="Klick mich!" on-click="self.value = 'Danke!'"/> self.bg_color = 'red' self.bg-color = 'red' <label value="Klick mich!" on-click="self.value = `self.event.button`"/> 3935042698_v01.book Seite 116 Freitag, 14. Oktober 2005 3:31 15
Eingebettete Skripte 117 3.5.2 Der Dsp-Namensraum In den Skripten gibt es den Dsp-Namensraum, in dem Sie alle ADL-Ele- mente, denen Sie mit id einen Namen gegeben haben, wiederfinden. Um ein Element anzusprechen, muss nur auf den entsprechenden Namen im Dsp- Namensraum zugegriffen werden. 3.5.3 Das <script>-Tag Neben Action-Handler können Sie Skripte auch innerhalb des <script>- Tags verwenden. Es gibt das Attribut uri, um ein Skript aus einer Datei zu la- den. Sie können den Code aber auch direkt in das <script>-Tag schreiben. Ein Desklet darf beliebig viele <script>-Tags enthalten. Innerhalb des <script>-Tags müssen von XML reservierte Zeichen wie üb- lich mit Entitäten umschrieben werden (also z.B. &lt; für <). Um das zu um- gehen und Skripte wie gewohnt schreiben zu können, lässt sich der Code in einem CDATA-Block vor dem Parser verstecken: <label id="mylabel" value="Klick den Anderen!"/> <label y="20" value="Klick mich!" on-click="Dsp.mylabel.value = 'Danke!'"/> <display> <label value="Klick mich!" on-click="hello()"/> <script uri="scripts/utilities.script"/> <script> def hello(): print "Hello World!" </script> </display> 3935042698_v01.book Seite 117 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 118 Mit dem uri-Attribut eingebundene Skripten sind nicht davon betroffen und können wie gewohnt geschrieben werden. 3.5.4 Vordefinierte Funktionen Um das Schreiben von Skripten zu vereinfachen, sind in der Skript-Umge- bung neben den in Python eingebauten Funktionen bereits einige Funktio- nen vordefiniert, die benutzt werden können. 1. add_timer (interval, callback, ...) Diese Funktion richtet einen Timer ein, der nach mit interval angegebe- nen Millisekunden die Funktion callback aufruft. Nach callback können beliebig viele weitere Argumente folgen, die alle an die Funktion mit- übergeben werden. Wenn callback als Rückgabewert Tru e zurückgibt, dann wird der Timer nach Ablauf des Intervalls erneut aktiv, anderenfalls nicht. 2. launch (command) Das in command angegebene Shell-Kommando wird im Hintergrund ausgeführt, wobei der User zuvor gefragt wird, ob er die Ausführung ein- malig erlauben, dauerhaft erlauben oder zurückweisen will. 3. Unit (value, unit) Koordinaten und Größenangaben müssen immer vom Typ Unit sein und werden mit diesem Konstruktor erzeugt. Für den Parameter unit stehen CM für Zentimeter, IN für Zoll, PERCENT für Prozent, PT für Punkt und PX für Pixel zur Verfügung. Unit-Objekte können wie normale Zahlen addiert, subtrahiert, multipliziert und dividiert werden. Verwenden Sie den Unit-Konstruktor ohne Argumente, um den Wert auf undefiniert zu setzen, damit er sich dynamisch anpassen kann. Das folgende Beispiel illustriert die Verwendung der Funktionen. <script><![CDATA[ def hello(): print "Hello World!" ]]></script> 3935042698_v01.book Seite 118 Freitag, 14. Oktober 2005 3:31 15
Konfigurationsdialoge 119 3.6 Konfigurationsdialoge Viele Applets lassen sich durch den Benutzer über einen Konfigurationsdi- alog konfigurieren. Ein solcher Dialog muss jedoch nicht mühsam von Hand programmiert werden, sondern wird anhand weniger XML-Elemente von gDesklets automatisch erzeugt. In diesem Abschnitt lernen Sie, wie solche Dialoge gebaut werden. 3.6.1 Einbinden von Dialogen Der Konfigurationsdialog wird mit dem <prefs>-Container in das Applet eingebaut. Alles, was zwischen den <prefs>-Tags steht, beschreibt den In- halt des Dialogs. Wie Sie im Beispiel sehen, können Objekte, z.B. Elementattribute, mit dem bind-Attribut an eine Konfigurationsoption gebunden werden. Änderungen wirken sich dann sofort darauf aus und werden über Sitzungen hinweg ge- def mytimer(): Dsp.mylabel.x = Dsp.mylabel.x + Unit(2, CM) return True add_timer(500, mytimer) launch("firefox http://www.gdesklets.org") <display> <label id="mylabel" value="Hallo!"/> <prefs> <color label="Die Textfarbe:" bind="Dsp.mylabel"/> </prefs> </display> 3935042698_v01.book Seite 119 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 120 speichert, ohne dass Sie als Programmierer sich darum kümmern müssen. Es ist möglich, jedes in Skripten ansprechbare Objekt an eine Konfigura- tionsoption zu binden, sofern es sowohl lesbar wie auch beschreibbar ist. 3.6.2 Konfigurationswidgets Weil es viele unterschiedliche Datentypen gibt, muss es auch verschiedene XML-Tags für Konfigurationswidgets geben. Vielen sind jedoch die in Ta- belle 3.13 aufgelisteten Attribute gemein. <page> Dieses Element ist ein Container für andere Konfigurationswidgets und unterstützt nur das label-Attribut. Auf dem Bildschirm erscheint für jedes <page>-Element eine Seite mit Reiter. Attribut Beschreibung bind Das zu konfigurierende Objekt callback Die Callback-Funktion, die aufgerufen wird, sobald der Wert vom Benutzer geändert wurde. Die Callback-Funktion muss als Argumente den Namen des gebundenen Objekts sowie den neuen Wert annehmen. enabled Der Status des Konfigurationswidgets. Wenn das Attribut auf False gesetzt ist, dann erscheint das Widget ausge- graut und reagiert nicht auf Benutzereingaben. Durch Skripte kann der Wert zur Laufzeit geändert werden. Voreinstellung: Tr u e help Der Hilfetext, der als Tooltip erscheint, wenn die Maus über dem Widget verharrt id Der eindeutige identifizierende Namen, über den aus Skrip- ten heraus auf das Element und dessen Attribute zugegrif- fen werden kann label Der Text, der normalerweise neben dem Konfigurationswid- get steht Tabelle 3.13: Attribute der Konfigurationswidgets 3935042698_v01.book Seite 120 Freitag, 14. Oktober 2005 3:31 15
Konfigurationsdialoge 121 <title> Auch das <title>-Element unterstützt nur das label-Attribut. Es kann als Überschrift für Abschnitte verwendet werden, um den Dialog optisch über- sichtlicher zu gestalten. <boolean> Dieses Element wird als Checkbox dargestellt, mit der boolesche Werte ge- ändert werden können. <prefs> <page label="Erste Seite"> <boolean label="Foo:" bind="foo"/> </page> <page label="Zweite Seite"> <boolean label="Bar:" bind="bar"/> </page> </prefs> <prefs> <title label="Erscheinungsbild"/> <uri label="Hintergrundbild:" bind="Dsp.w.bg_uri"/> <enum label="Stil:" bind="style"> <item label="Klassisch" value="classic"/> <item label="Modern" value="modern"/> </enum> </prefs> <boolean label="Zeitzone anzeigen" bind="Dsp.tz.visible"/> 3935042698_v01.book Seite 121 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 122 <color> Farben lassen sich bequem mit diesem Element konfigurieren. <enum> Dieser Container wird als Drop-down-Menü für Aufzählungen dargestellt. Die Kind-Elemente müssen <item>-Elemente sein. <float> Gleitkommawerte können mit diesem Element verändert werden. Zusätz- lich zu den allgemeinen Attributen lassen sich auch die in Tabelle 3.14 ge- nannten Attribute verwenden. <color label="Textfarbe:" bind="Dsp.tz.color"/> <enum label="Zifferblatt" bind="clockface"> <item label="Standard" value="clock.png"/> <item label="gDesklets" value="gdclock.png"/> <item label="Großvaters Taschenuhr" value="pocketwatch.png"/> </enum> Attribut Beschreibung digits Die Anzahl der gezeigten Nachkommastellen (nicht bei <integer>) Voreinstellung: 2 min Der kleinste akzeptierte Wert Voreinstellung: 0 max Der größte akzeptierte Wert Voreinstellung: 9999 Tabelle 3.14: Attribute von <float>, <integer> und <unit> <float label="Größe" bind="size" min="0.5" max="50.0" help="Die Größe der Uhr"/> 3935042698_v01.book Seite 122 Freitag, 14. Oktober 2005 3:31 15
Konfigurationsdialoge 123 <font> Dieses Element stellt einen Auswahldialog für Schriften zur Verfügung. <integer> Dieses Element ist ähnlich wie <float>, unterstützt jedoch nur Ganzzahlen. Bis auf das digits-Attribut stehen alle Attribute aus Tabelle 3.14 zur Verfü- gung. <item> Das <item>-Tag kann nur in einem <enum>-Container verwendet werden. Bis auf die in Tabelle 3.15 gezeigten Attribute unterstützt es keine weiteren Attribute. <string> Zeichenketten können mit dem <string>-Element verändert werden. <font label="Schrift:" bind="Dsp.tz.font"/> <integer label="MTU:" bind="mtu" min="46" max="1500"/> Attribut Beschreibung label Der Text, der im Auswahlmenü erscheint value Der Wert, auf den das vom <enum>-Container gebundene Objekt gesetzt wird, wenn der Benutzer dieses Element auswählt. Der Wert ist eine Zeichenkette. Tabelle 3.15: Attribute von <item> <string label="Zeitzone" bind="time.timezone" help="Die verwendete Zeitzone"/> 3935042698_v01.book Seite 123 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 124 <unit> Dieses Element konfiguriert geometrische Werte, die in gDesklets mit Ein- heiten versehen sein können. Es unterstützt wie <float> die in Tabelle 3.14 genannten Attribute. Der Benutzer kann einen Wert angeben und eine Ein- heit auswählen. <uri> Das <uri>-Element stellt einen Auswahldialog für Dateien zur Verfügung. 3.6.3 Der Prefs-Namensraum Ähnlich dem Dsp-Namensraum in Kapitel 3.5.2 gibt es den Prefs-Namens- raum, über den alle Konfigurationselemente angesprochen werden können, sofern mit dem id-Attribut ein eindeutiger Name zugewiesen wurde. Der Zugriff auf Attribute erfolgt genauso, wie Kapitel 3.5.2 es für den Dsp-Na- mensraum beschreibt. <unit label="Breite:" bind="Dsp.win.width" min="0.5" max="50.0" help="Die Breite des Applets"/> <uri label="Hintergrundbild:" bind="Dsp.win.bg_uri"/> <prefs> <boolean id="showtz" label="Zeitzone zeigen" bind="Dsp.tz.visible"/> </prefs> <script> ... Prefs.showtz.enabled = False ... </script> 3935042698_v01.book Seite 124 Freitag, 14. Oktober 2005 3:31 15
Controls einbinden 125 3.7 Controls einbinden Skripte werden in einer abgeschirmten Umgebung ausgeführt, die aus Sicherheitsgründen keine Python-Module importieren darf. Um dennoch auf das System zugreifen zu können, gibt es Controls. Das sind wiederver- wendbare, lokal installierte Module mit privilegiertem Code, der keinen Einschränkungen unterliegt. 3.7.1 Schnittstellen Jedes Control implementiert eine oder mehrere Schnittstellen. Auf diese Weise sind verschiedene Controls, welche die gleichen Schnittstellen anbie- ten, gegeneinander austauschbar, z.B. eine alte Version gegen eine effizien- tere neue Version. Damit dies möglich wird, werden Controls niemals di- rekt, sondern nur über die Schnittstellen eingebunden. Jede Schnittstelle (Interface) besitzt einen global eindeutigen automatisch generierten Bezeichner (Interface-ID), wie z.B. ITime:7qktelp6tw29ve5p8 q3lxn6bs-2. Wenn ein Desklet ein Control anfordert, gibt es den Schnittstel- lenbezeichner an und verlässt sich darauf, ein für die geforderte Schnittstelle kompatibles Control zu bekommen. 3.7.2 Der Control-Inspector Natürlich will niemand sich die Schnittstellenbezeichner merken oder von Hand abschreiben. Für den Umgang mit Controls gibt es daher den Control- Inspector. Nach dem Start der gDesklets-Shell kann zwischen zwei Ansichten gewech- selt werden: den Displays und den Controls. Die Controls-Ansicht listet alle installierten Controls auf (siehe Abbildung 3.4). Ein Doppelklick auf ein Control öffnet ein weiteres Fenster -- den Control-Inspector, der in Abbil- dung 3.5 zu sehen ist. 3935042698_v01.book Seite 125 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 126 Abb. 3.4: Die Controls-Ansicht der gDesklets-Shell Abb. 3.5: Der Control-Inspector Dieser zeigt neben den Bezeichnern der implementierten Schnittstellen auch Dokumentation über das Control selbst an. Um Abschreibfehler zu vermei- den, können die Schnittstellenbezeichner mit der Maus markiert und in die Zwischenablage kopiert werden. 3935042698_v01.book Seite 126 Freitag, 14. Oktober 2005 3:31 15
Controls einbinden 127 3.7.3 Controls verwenden Mit dem <control>-Tag werden Controls in ein Desklet eingebunden. Das id-Attribut legt dabei fest, unter welchem Namen das Control in den Skrip- ten ansprechbar ist. Mit dem interface-Attribut wird der Bezeichner der ge- wünschten Schnittstelle angegeben: Wie im Beispiel zu sehen ist, sind auch Controls Objekte mit Attributen. Die Attribute können lesbar, beschreibbar oder beides sein. Um auf ein Control zuzugreifen, werden in Skripten die Attribute gesetzt bzw. ausgelesen. Im obigen Beispiel etwa wird die aktuelle Uhrzeit aus dem time-Attribut ausge- lesen. Oft muss dasselbe Attribut in regelmäßigen oder unregelmäßigen Ab- ständen immer wieder neu ausgelesen werden. Die Uhrzeit etwa ändert sich jede Sekunde. Um nicht aktiv auf Änderungen prüfen zu müssen, unter- stützen Controls bei vielen1 Attributen ein passives (push-basiertes) Verfah- ren. Sie werden automatisch über Änderungen benachrichtigt und müssen nur eine Callback-Funktion an das Attribut binden. Zu diesem Zweck besitzt jedes Control die bind-Methode. Diese Methode verwendet als Argumente den Namen des Attributs als Zeichenkette sowie die aufzurufende Callback- Funktion, die als Argument den Attributwert entgegen nimmt. <display> <!-- Das Time Control kann unter anderem die aktuelle Uhrzeit ausgeben. --> <control id="ctrl" interface="ITime:7qktelp6tw29ve5p8q3lxn6bs-2"/> <label value="Klick mich!" on-click="self.value = str(ctrl.time)"/> </display> 1. Ob ein Attribut das unterstützt, hängt davon ab, ob der Autor des Controls die Unterstützung implementiert hat (siehe auch Kapitel 3.8.3). 3935042698_v01.book Seite 127 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 128 Eine komplette Digitaluhr ist damit in wenigen Zeilen geschrieben: 3.8 Controls schreiben Nachdem Sie bis jetzt nur vorgefertigte Controls benutzt haben, lernen Sie in diesem Abschnitt, wie Sie neue Controls schreiben. Damit gDesklets das neue Control auch finden kann, muss es in ~/.gdesk- lets/Controls abgelegt sein. 3.8.1 Schnittstellendefinition Jedes Control muss mindestens eine Schnittstelle implementieren. Die Schnittstellen sind ebenfalls Python-Module, die aber per Konvention mit einem "I" beginnen, wie z.B. ITime. Außerdem muss jede Schnittstelle von libdesklets.controls.Interface abgeleitet sein. Neben Kommentaren darf eine Schnittstellenklasse nur Attribute und deren Zugriffsrechte enthalten. <display window-flags="below"> <control id="mytime" interface="ITime:7qktelp6tw29ve5p8q3lxn6bs-2"/> <label id="mylabel" font="Monospace 16"/> <script><![CDATA[ def mycallback(value): Dsp.mylabel.value = "%02d:%02d:%02d" % value mytime.bind("time", mycallback) ]]></script> </display> 3935042698_v01.book Seite 128 Freitag, 14. Oktober 2005 3:31 15
Controls schreiben 129 Die Zugriffsrechte sind Objekte aus libdesklets.controls.Permission und werden hinter jede Attributdefinition geschrieben. Die Zugriffsrechte sind: 1. Permission.READ -- das Attribut ist nur lesbar. 2. Permission.WRITE -- das Attribut ist nur beschreibbar. 3. Permission.READWRITE -- das Attribut kann sowohl gelesen als auch geschrieben werden. Eine komplette Schnittstellenklasse sieht wie folgt aus: 3.8.2 Implementieren von Attributen Controls sind Python-Klassen, die von der Basisklasse libdesklets.con- trols.Control und den implementierten Schnittstellen abgeleitet sind. Wich- tig ist, dass der Superkonstruktor der Control-Basisklasse aufgerufen wird. Jedes Control liegt in einem eigenen Verzeichnis, das neben den implemen- tierten Schnittstellen mindestens noch die __init__.py-Datei enthalten muss, welche das Control initialisiert und eine Funktion get_class anbietet, die die Klasse des Controls zurückgibt. Eine gültige Verzeichnisstruktur für ein Control sieht z.B. wie folgt aus: from libdesklets.controls import Interface from libdesklets.controls import Permission class ITime(Interface): # Attribute und ihre Zugriffsrechte time = Permission.READ date = Permission.READ timezone = Permission.READWRITE Time/ ITime.py __init__.py 3935042698_v01.book Seite 129 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 130 Natürlich dürfen auch weitere Dateien im Verzeichnis oder in Unterver- zeichnissen enthalten sein, weil das Verzeichnis ein normales Python- Package ist. Um die Schnittstellen zu implementieren, muss für jedes Attribut ein gleich- namiges Python-Property angelegt werden. Das nächste Beispiel zeigt das Gerüst des Tim e -Controls, welches bei gDesklets bereits mitgeliefert wird. from libdesklets.controls import Control from ITime import ITime class Time(Control, ITime): def __init__(self): Control.__init__(self) ... ... def __set_timezone(self, tz): ... def __get_timezone(self): return self.__timezone ... timezone = property(__get_timezone, __set_timezone, doc = "the timezone") time = ... ... def get_class(): return Time 3935042698_v01.book Seite 130 Freitag, 14. Oktober 2005 3:31 15
Controls schreiben 131 3.8.3 Die Control-Basisklasse Jedes Control muss von der Basisklasse libdesklets.controls.Control abge- leitet sein und besitzt deshalb eine Reihe von Methoden, die zum Schreiben von Controls verwendet werden können. 1. _add_timer (interval, callback, *args) Diese Methode startet die in callback referenzierte Funktion nach inter- val Millisekunden und gibt eine Referenz zurück, mit der der Timer mit _remove_timer entfernt werden kann. Falls der Callback Tr u e zurück- gibt, wird er nach interval Millisekunden erneut aufgerufen. Außerdem können beliebig viele Argumente an die Callback-Funktion mit überge- ben werden. 2. _remove_timer (ident) Der mit ident angegebene Timer wird mit dieser Methode wieder ent- fernt und nicht mehr ausgeführt. 3. _shutdown () Diese Methode kann überschrieben werden, wenn ein Control noch auf- räumen muss, bevor es geschlossen wird. 4. _update (prop) Damit ein Attribut mit bind (siehe Kapitel 3.7.3) beobachtet werden kann, muss diese Methode nach jeder Änderung des Attributwerts auf- gerufen werden. Die mit prop übergebene Zeichenkette bezeichnet dabei den Namen des geänderten Attributs, wie er auch bei bind angegeben wird. 3.8.4 Das Testskript test-control.py Damit neue Controls gleich getestet werden können, ohne sie erst in einem Desklet verwenden zu müssen, bringt gDesklets das Skript test-control.py mit, welches Sie im Installationsverzeichnis finden. Als Argument für test-control.py geben Sie den Pfad zum Verzeichnis des Controls an, zum Beipiel: $ /usr/lib/gdesklets/test-control.py Time/ 3935042698_v01.book Seite 131 Freitag, 14. Oktober 2005 3:31 15
3 -- Applets bauen mit gDesklets 132 Nach dem Start listet das Skript die implementierten Schnittstellenbezeich- ner sowie die zur Verfügung stehenden Attribute auf. Anschließend geht es in den Python-Direktmodus, damit das Control getestet werden kann. Sie können über das Objekt ctrl darauf zugreifen. ITime:7qktelp6tw29ve5p8q3lxn6bs-2 date r the current date (y, m, d) time r the current time (h, m, s) timezone rw the timezone Use 'ctrl' to access the control. Press Ctrl+D to quit. >>> 3935042698_v01.book Seite 132 Freitag, 14. Oktober 2005 3:31 15
133 4 Python und die Java-Welt Von Wolfgang Weitz Java hat sich inzwischen als eine der verbreitetsten Universal-Programmier- sprachen etabliert. Mindestens so interessant wie die Sprache selbst ist aber die kaum mehr überschaubare Zahl der für die Java-Plattform verfügbaren Klassenbibliotheken, Frameworks und Server-Software für die unterschied- lichsten Anwendungszwecke. Warum also nicht Python und Java miteinan- der kombinieren? Sinnvolle Beispiele für einen solchen gemeinsamen Ein- satz gibt es viele: Sie benötigen für Ihr Projekt eine Softwarekomponente, die nur in Java verfügbar ist, möchten aber deswegen nicht von Python auf Java als Implementierungssprache umsteigen. Sie entwickeln ein Softwaresystem in Java, würden aber wegen der höhe- ren Entwicklungsgeschwindigkeit gerne Python zur schnellen Realisie- rung eines Prototyps oder zum Erstellen automatisierter Tests einsetzen. Viele Anwendungen bieten dem Benutzer die Möglichkeit, ihre Funk- tionalität mit einer eingebauten Skriptsprache individuell anzupassen und zu ergänzen, beispielsweise VisualBasic in den Microsoft Office- Produkten oder EmacsLisp im Editor Emacs. Durch die Integration einer verfügbaren Skriptsprache wie Python können Sie Ihre Java-Anwen- dung mächtiger und flexibler machen, ohne eine eigene Makrosprache implementieren zu müssen. Die nachfolgenden Abschnitte zeigen anhand von Beispielen verschiedene Möglichkeiten, die Vorzüge von Python mit denen der Java-Welt zu verbin- den. 4.1 Jython Jython ist eine reine Java-Implementierung des Python-Interpreters und macht Python damit überall verfügbar, wo eine Java-Ablaufumgebung be- reitsteht. Der Python-Code wird dazu in Java Bytecode übersetzt und auf der 3935042698_v01.book Seite 133 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 134 Java Virtual Machine (JVM) ausgeführt. Bislang reicht die Ausführungs- geschwindigkeit nicht an die des in C implementierten Standard-Python-In- terpreters heran, dafür bietet Jython aber eine hervorragende Integration in die Java-Welt. Die stabile Jython-Version 2.1 ist bereits im Dezember 2001 erschienen. Trotzdem zählt sie noch immer zu den schnellsten Skriptsprachen auf der JVM. Dies bestätigt auch ein Artikel von David Kearns, in dem er Jython mit sieben anderen aktuellen JVM-Skriptsprachen vergleicht1. 4.1.1 Vorbereitungen Jython steht auf der Jython-Homepage (http://www.jython.org) zum Herun- terladen bereit. Wie bereits erwähnt benötigt Jython eine Java-Ablaufumge- bung, die z.B. unter http://java.sun.com für verschiedene Betriebssysteme verfügbar ist. In den folgenden Beispielen kommt die aktuelle Jython-Version 2.1 zum Einsatz. Zur Installation wird die Datei jython-21.class ausgeführt: Falls Jython 2.1 das verwendete Betriebssystem nicht klar identifizieren kann, erscheint vor dem eigentlichen Installationsprozess eine entsprechen- de Abfrage (Abb. 4.1). Abb. 4.1: Betriebssystemauswahl im Jython 2.1-Installer 1. http://www.javaworld.com/javaworld/jw-03-2005/jw-0314-scripting.html java jython-21 3935042698_v01.book Seite 134 Freitag, 14. Oktober 2005 3:31 15
Jython 135 In den wenigen nachfolgenden Schritten erfragt das Installationsprogramm Installationsumfang und Sprache, überprüft die installierte Java-Version, zeigt die Nutzungsbedingungen und bietet die Wahl des Installationsverzeichnisses an. Nach abgeschlossener Installation steht im gewählten Installationsverzeich- nis ein Skript zum Starten von Jython zur Verfügung (jython unter UNIX/ Linux, jython.bat unter Windows). Zur Entwicklung von Jython-Programmen kann ein beliebiger Texteditor benutzt werden. Gerade in der Kombination mit Java bietet sich aber die Verwendung einer der bekannten integrierten Java-Entwicklungsumgebun- gen an, die mitunter auch Python unterstützen. Als kostenlos für verschie- dene Betriebssysteme erhältliches Beispiel sei hier Eclipse (http://www. eclipse.org) genannt. Das Plug-in PyDev (http://pydev.sourceforge.net) er- gänzt Eclipse um eine gute allgemeine Python-Integration und hat inzwi- schen auch erste spezielle Funktionalität für Jython. 4.1.2 Jython interaktiv Python-Entwickler schätzen schon lange die Möglichkeit, den Python-Inter- preter auch interaktiv nutzen können. Ohne den Umweg über eine vorher zu erstellende Quelldatei kann man seine Python-Anweisungen direkt einge- ben und bekommt sofort das Ergebnis gezeigt. Auch Jython bietet diesen interaktiven Modus an, und durch die nahtlose Integration mit der Java-Plattform können nun auch Java-Objekte von der Jython-Kommandozeile aus erzeugt und getestet werden -- ohne den um- ständlichen "editieren/kompilieren/starten"-Zyklus. Java-Pakete lassen sich dazu wie von Python-Modulen gewohnt importieren und dann gleich nut- zen. Wie man unter Verwendung der bekannten Python-Syntax eine Instanz der Java-Klasse HashMap aus dem Paket java.util (vergleichbar mit einem Python-Dictionary) erzeugen und verwenden kann, zeigt die Beispielsit- zung in Listing 4.1. 3935042698_v01.book Seite 135 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 136 Neben den HashMap-Methoden put() zum Einfügen und get() zum Abfra- gen von Einträgen erlaubt Jython zusätzlich die bei Python-Dictionaries üb- liche Schreibweise mit eckigen Klammern. Auch das Löschen von Einträ- gen mit del wird unterstützt. Dadurch vergisst man leicht, dass man tatsächlich mit einem Java-Objekt arbeitet. So stehen Python-Dictionary- Methoden wie items(), keys() und values() naturgemäß nicht zur Verfü- gung, sind allerdings leicht durch die entsprechenden Java-Methoden von HashMap zu ersetzen. 4.1.3 Ereignisbehandlung und Eigenschaften Im vorherigen Beispiel wurde ein Java-Objekt von Jython aus erzeugt und angesteuert. Viele Java Framework-Komponenten erfordern jedoch eine Umkehrung der Verantwortung für den Kontrollfluss nach dem Hollywood- Prinzip ("don't call us -- we'll call you"). Dazu übergibt der Entwickler der entsprechenden Komponente ein "Listener"-Objekt, dessen in der entspre- chenden Schnittstellenspezifikation festgelegte Methoden die Komponente aufruft, sobald das entsprechende Ereignis eintritt. Listing 4.2 zeigt ein einfaches Java-Programm, welches ein kleines Fenster mit einem Knopf erzeugt (Abbildung 4.2). Sobald der Knopf angeklickt wird, gibt das Programm die Zeichenkette "Klick" auf seiner Konsole aus. C:\Programme\jython-2.1> jython Jython 2.1 on java1.5.0_01 (JIT: null) Type "copyright", "credits" or "license" for more information. >>> from java.util import HashMap >>> h = HashMap() >>> h.put(17, "siebzehn") >>> h.put(4, "vier") >>> h.get(4) 'vier' >>> h[8] = "acht" >>> h {17=siebzehn, 4=vier, 8=acht} >>> h[17] 'siebzehn' Listing 4.1: Interaktive Jython-Sitzung 3935042698_v01.book Seite 136 Freitag, 14. Oktober 2005 3:31 15
Jython 137 Abb. 4.2: Java-Fenster mit Knopf Der Aufbau des Programms ist recht leicht nachzuvollziehen. In der main()- Methode wird lediglich eine Instanz der Klasse SwingKlickFenster erzeugt und deren start()-Methode aufgerufen. Hier werden dann ein Fenster (JFrame) und ein Knopf (JButton) erzeugt und der Knopf der Inhaltsfläche des Fensters hinzugefügt. import javax.swing.*; import java.awt.event.*; public class SwingKlickFenster implements ActionListener { public void start() { JFrame fenster = new JFrame("Ein Fenster"); fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton knopf = new JButton("Klick mich!"); fenster.getContentPane().add(knopf); knopf.addActionListener(this); fenster.pack(); fenster.setVisible(true); } public void actionPerformed(ActionEvent event) { System.out.println("Klick!"); } public static void main(String[] args) { SwingKlickFenster app = new SwingKlickFenster(); app.start(); } }Listing 4.2: Swing-Fenster mit Knopf in Java 3935042698_v01.book Seite 137 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 138 Der Knopf muss nun noch erfahren, welches Objekt er durch Aufruf der Me- thode actionPerformed() benachrichtigen soll, wenn er gedrückt wurde. Da das SwingKlickFenster-Objekt diese Aufgabe selbst übernehmen soll, regis- triert es sich durch die Zeile knopf.addActionListener(this) beim Knopf als dessen zuständiger Listener und erklärt dazu im Kopf der Klasse wahr- heitsgemäß, dass es die Schnittstelle ActionListener implementiert. Schließlich wird der Zusammenbau des Beispielfensters abgeschlossen und das Fenster sichtbar gemacht. In Listing 4.3 ist zum Vergleich die gleiche Anwendung in Jython zu sehen. In einer Textdatei swingbutton.py gespeichert kann sie mit dem Kommando jython swingbutton.py ausgeführt werden. Abgesehen davon, dass die Jython-Version kürzer und dadurch etwas über- sichtlicher ist, sieht man schon in diesem kleinen Beispiel an mehreren Stel- len, wie Jython die Java-Integration unterstützt. Zunächst hatten wir im Java-Beispiel festgestellt, dass die Knopf-Kom- ponente (JButton) ein Objekt benötigt, welches die Schnittstelle Action- Listener implementiert und eine Methode mit dem vorgegebenen Namen actionPerformed bereitstellt. Beides ist in der Jython-Version nicht gleich wiederzufinden. Stattdessen wurde hier eine einfache Jython-Funktion mit dem selbst gewählten Namen klick an den Knopf gebunden, und dies nicht import javax.swing as swing def klick(event): print "Klick!" fenster = swing.JFrame("Ein Fenster") fenster.setDefaultCloseOperation(swing.JFrame.EXIT_ON_CLOSE) knopf = swing.JButton("Klick mich!", actionPerformed=klick) fenster.contentPane.add(knopf) fenster.pack() fenster.visible = 1 Listing 4.3: Swing-Fenster mit Knopf in Jython 3935042698_v01.book Seite 138 Freitag, 14. Oktober 2005 3:31 15
Jython 139 durch separaten Aufruf der Methode knopf.addActionListener(), sondern übersichtlich als Schlüsselwortparameter actionPerformed beim Erzeugen des Knopfs. Jython erkennt dazu selbständig, welche Schnittstellen eine Klasse zur Re- aktion auf Ereignisse unterstützt, und stellt automatisch Attribute mit den Namen der entsprechenden Methoden bereit -- im Beispiel actionPerformed aus der Schnittstelle ActionListener, die JButton für Benachrichtigungen über einen Knopfdruck verwendet. Diese Attribute können bei der Erzeugung einer neuen Instanz mit Schlüs- selwortparametern oder aber auch später durch einfache Zuweisung an die gewünschte Funktion (oder Methode) gebunden werden. Im Beispiel wäre das Einrichten des Knopfs also auch in zwei Schritten möglich gewesen: Auch das dem Java-Verhalten ähnlichere Übergeben eines Objekts, das eine passende Methode actionPerformed bereitstellt, wäre möglich. Weitere hilfreiche Eigenschaften von Jython zeigen sich in der letzten Zeile des Beispiels im Vergleich mit der entsprechenden Java-Zeile Java-Klassen definieren häufig Eigenschaften (properties), die mit Hilfe von set- und get-/is-Methoden belegt bzw. gelesen werden. Im Beispiel besitzt die Klasse JFrame die wahrheitswertige Eigenschaft "visible", das JFrame- Fenster wird also nach dem Aufruf von setVisible(true) angezeigt und nach setVisible(false) wieder unsichtbar. Zur Abfrage dieser Eigenschaft gibt es zudem eine Methode isVisible(). Jython erkennt solche Eigenschaften und stellt gleichnamige Attribute be- reit, die wie in Python üblich gelesen und per Zuweisung geändert werden knopf = swing.JButton("Klick mich!") knopf.actionPerformed = klick fenster.visible = 1 fenster.setVisible(true); 3935042698_v01.book Seite 139 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 140 können. Die Zuweisung fenster.visible = 1 ist also gleichbedeutend mit dem Methodenaufruf fenster.setVisible(1). Da die Java-seitige setVi- sible()-Methode letztlich einen Wahrheitswert (true oder false) erwartet, wandelt Jython die zugewiesene Ganzzahl gemäß der Python-Konvention "Null ist falsch und der Rest ist wahr" um. 4.1.4 Beispiel: Ein Überraschungs-Mediaplayer Die Möglichkeit zur Nutzung des Swing-Frameworks allein wird für den ge- übten Python-Entwickler noch kein Grund sein, sich Jython anzusehen -- schließlich gibt es für Standard-Python eine ausreichende Auswahl von GUI-Frameworks und auch sonst kann die Python-Standardbibliothek gut mithalten. Das folgende Beispiel verwendet ein Java-Framework, das auf der Python- Seite bisher keine so leistungsstarke Entsprechung hat: das Java Media Framework (JMF)1. Es wird ein Jython-Skript ueberraschung.py entwickelt, dem man beim Start als Kommandozeilenparameter einen Suchausdruck und eine Liste von Verzeichnissen übergibt und das 1. die Verzeichnisse (einschließlich der Unterverzeichnisse) nach Dateien durchsucht, auf deren Name der Suchausdruck passt, und 2. danach eine zufällig ausgewählte dieser Audio- oder Videodateien ab- spielt, wobei auch Bedienelemente zum Vor-/Rückspulen, Anhalten und zur Regelung der Lautstärke bereitgestellt werden sollen (Abbildung 4.3). Hier lassen sich die Stärken von Python und Java gut kombinieren: Das Durchlaufen der Verzeichnisse und Identifizieren passender Dateien lässt sich in Python in wenigen Zeilen umsetzen, und beim Abspielen verschie- dener Audio- und Videoformate hilft das Java Media Framework. 1. http://java.sun.com/products/java-media/jmf/ 3935042698_v01.book Seite 140 Freitag, 14. Oktober 2005 3:31 15
Jython 141 Abb. 4.3: ueberraschung.py beim Abspielen eines Zufallsvideos Erste Teilaufgabe: passende Dateien finden Dieser Teil lässt sich leicht mit Hilfe der walk()-Funktion aus der Python- Standardbibliothek lösen, wie Listing 4.4 zeigt. Diese Funktion durchläuft, ausgehend von einem übergebenen Startverzeichnis, das Verzeichnis selbst und alle Unterverzeichnisse. Dabei wird für jedes besuchte Verzeichnis die als zweiter Parameter übergebene Funktion (oder allgemeiner: das über- gebene aufrufbare Objekt) aufgerufen und erhält dabei als Parameter den aktuellen Verzeichnispfad und eine Liste des Verzeichnisinhalts sowie einen frei wählbaren Wert, der ursprünglich als dritter Parameter beim Aufruf von walk() mitgegeben wurde. Letzterer wird hier nicht verwendet und deshalb der Form halber mit None belegt. 3935042698_v01.book Seite 141 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 142 Zur Inspektion der Verzeichnisse und ihrer Inhalten übergeben wir walk() eine Instanz der Klasse Sammler, die zuvor mit dem gewünschten Suchaus- druck parametrisiert wurde. Sammler verfügt über eine __call__-Methode, so dass eine Instanz dieser Klasse wie eine gewöhnliche Python-Funktion aufgerufen werden kann. Anders als eine Funktion können solche Objekte aber natürlich über Methoden und Instanzvariablen verfügen, in denen sie ihren Zustand über mehrere Aufrufe hinweg festhalten können. import os, random, sys, re class Sammler: def __init__(self, muster): self.sammlung = [] self.passt = re.compile(muster).search def __call__(self, arg, verzeichnis, eintraege): brauchbar = [ os.path.join(verzeichnis,e) for e in eintraege if self.passt(e) ] self.sammlung.extend(filter(os.path.isfile, brauchbar)) def getSammlung(self): return self.sammlung if __name__ == "__main__": suchMuster = sys.argv[1] verzeichnisListe = sys.argv[2:] sammler = Sammler(suchMuster) for verzeichnis in verzeichnisListe: os.path.walk(verzeichnis, sammler, None) sammlung = sammler.getSammlung() if sammlung: meinPlayer = MeinPlayer("file:"+random.choice(sammlung)) meinPlayer.start() else: print "Keine passende Datei gefunden" Listing 4.4: ueberraschung.py (1) -- Finden einer passenden Mediendatei 3935042698_v01.book Seite 142 Freitag, 14. Oktober 2005 3:31 15
Jython 143 In unserem Beispiel besteht dieser Zustand aus dem anfangs übergebenen Suchmuster und der Liste der bisher aufgesammelten Dateipfade. Das Suchmuster (ein regulärer Ausdruck) wird aus Effizienzgründen gleich in der __init__ -Methode kompiliert und seine gebundene search-Methode in der Instanzvariable self.passt gespeichert, so dass später ein einfaches self.passt(e) zum Testen eines Dateinamens e genügt. Die Filterung und Speicherung der von walk() angebotenen Verzeichnisein- träge ist in der __call__-Methode beschrieben. Zwei Zeilen genügen, um zunächst die zum Suchausdruck passenden Verzeichniseinträge zu identifi- zieren sowie einen absoluten Pfad (Verzeichnis + Dateiname) zusammenzu- bauen und dann aus der gewonnenen Liste nur diejenigen Einträge zur Sammlung hinzuzufügen, die tatsächlich Dateien bezeichnen (und nicht etwa Unterverzeichnisse). Zweite Teilaufgabe: Mediendatei präsentieren Nachdem alle vom Benutzer angegebenen Verzeichnisse auf diese Weise durchsucht wurden, enthält das sammler-Objekt die komplette Liste der in Frage kommenden Dateipfade. Diese wird nun mit Hilfe der Methode get- Sammlung() abgefragt und ein mit random.choice() zufällig ausgewählter Dateipfad wird einem neuen MeinPlayer-Objekt zum Abspielen übergeben. Die Realisierung der MeinPlayer-Klasse unter Verwendung des Java Media Frameworks zeigt Listing 4.5. Die Methode start() erzeugt in mittlerweile bekannter Weise ein Swing-Fenster, das mit drei GUI-Komponenten gefüllt wird: Im oberen Teil ("NORTH") erscheint eine einfache Textzeile (JLabel) mit dem Namen der abzuspielenden Datei. In der Mitte ("CENTER") wird die grafische Ausgabekomponente ("visual component") des vom JMF bereitgestellten Player-Objekts eingebaut, sofern eine solche existiert. Bei Videodateien repräsentiert diese Kom- ponente die "Leinwand" zur Darstellung des Films, während ein Player für Audiodateien in der Regel keine solche grafische Präsentation an- bietet. Am unteren Rand des Fensters ("SOUTH") wird -- so vorhanden -- die vom Player bereitgestellte grafische Komponente ("control panel compo- 3935042698_v01.book Seite 143 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 144 nent") mit verschiedenen Steuerelementen angezeigt, etwa eine Zeit- achse mit Positionierungsschieber, Lautstärkeregelung oder Start/Stopp- Knöpfe. import sys, synchronize import javax.swing as swing import javax.media as jm import java.awt as awt class MeinPlayer(jm.ControllerListener): def __init__(self, dateiURL): self.dateiURL = dateiURL def controllerUpdate(self, event): if isinstance(event, jm.EndOfMediaEvent): self.player.stop() self.player.deallocate() sys.exit() controllerUpdate = synchronize.make_synchronized( controllerUpdate) def start(self): dateiName = os.path.basename(self.dateiURL) fenster = swing.JFrame(self.dateiURL) fenster.defaultCloseOperation = swing.JFrame.EXIT_ON_CLOSE fenster.layout = awt.BorderLayout() self.player = player = \ jm.Manager.createRealizedPlayer( jm.MediaLocator(self.dateiURL)) player.addControllerListener(self) fenster.contentPane.add(swing.JLabel(dateiName), awt.BorderLayout.NORTH) filmAusgabe = player.getVisualComponent() Listing 4.5: ueberraschung.py (2) -- Abspielen einer Audio-/Videodatei 3935042698_v01.book Seite 144 Freitag, 14. Oktober 2005 3:31 15
Jython 145 Das Java Media Framework unterstützt eine große Anzahl unterschiedlicher Audio- und Videoformate. Die Bereitstellung eines konkreten, "gebrauchs- fertigen" Player-Objekts für eine gegebene Mediendatei leistet die Methode createRealizedPlayer() der JMF-Klasse Manager. Die Darstellung der Me- diendatei beginnt dann nach Aufruf der Player-Methode start(). Auch an diesem Beispiel lassen sich wieder einige Bemerkungen zum Zu- sammenspiel zwischen Jython und Java festmachen. Zunächst muss man wissen, dass ein JMF-Player nebenläufig ausgeführt wird (eigener Thread) und seine start()-Methode unmittelbar zurückkehrt. Um also beispielsweise Aufräumarbeiten bei Erreichen des Dateiendes aus- lösen zu können, ohne den Player bis dahin ständig nach seinem Zustand fragen zu müssen, kann man beim Player mit addControllerListener() ein Objekt registrieren, welches die JMF-Schnittstelle ControllerListener im- plementiert und eine Methode controllerUpdate() bereitstellt. Diese Me- thode wird bei jedem Zustandswechsel des Players aufgerufen und erhält als Parameter ein Ereignisobjekt, das die Art des Zustandswechsels be- schreibt. Solche Zustandswechsel umfassen sowohl verschiedene Phasen bei der Ini- tialisierung des Players wie auch bestimmte Ereignisse beim Abspielen des Mediums selbst, etwa das Erreichen des Endes oder einen manuellen Ein- griff des Benutzers, der beispielsweise die Vorführung angehalten oder das Medium vor- bzw. zurückgespult hat. Der Ereignismechanismus erlaubt es, if filmAusgabe: fenster.contentPane.add(filmAusgabe, awt.BorderLayout.CENTER) steuerEle = player.getControlPanelComponent() if steuerEle: fenster.contentPane.add(steuerEle, awt.BorderLayout.SOUTH) fenster.pack() fenster.visible = 1 player.start() Listing 4.5: ueberraschung.py (2) -- Abspielen einer Audio-/Videodatei 3935042698_v01.book Seite 145 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 146 in der controllerUpdate-Methode auf diese Situationen flexibel zu rea- gieren. controllerUpdate interessiert sich im Beispiel lediglich dafür, ob das über- gebene Ereignisobjekt vom Typ EndOfMediaEvent ist, also das Erreichen des Medienendes anzeigt, und beendet in diesem Fall die Vorführung und das Skript. Jython ermöglicht sowohl die Implementierung beliebiger Java-Schnittstel- len (in Java: "implements") als auch das Erben von Java-Klassen (in Java: "extends") mit der gewohnten Python-Syntax, also durch Aufzählung der Schnittstellen bzw. Oberklassen in runden Klammern hinter dem Jython- Klassennamen, wie oben anhand der Klasse MeinPlayer gezeigt. Da Java im Gegensatz zu Jython nur einfache Vererbung zulässt, dürfen auch Jython-Klassen (direkt oder indirekt) nur von höchstens einer Java- Klasse erben. Es können aber beliebig viele Java-Schnittstellen implemen- tiert werden und auch das Erben von "reinen" Jython-Klassen unterliegt -- wie in Python üblich -- nicht dieser Einschränkung. Wie bereits erwähnt, ruft das Player-Objekt im Beispiel die Methode con- trollerUpdate() bei jedem Zustandswechsel auf. Solche Zustandswechsel treten jedoch unter Umständen so schnell hintereinander auf, dass wegen der Nebenläufigkeit in JMF überlappende Aufrufe der Methode nicht ausge- schlossen werden können. Java verfügt dazu über das Schlüsselwort synchronized, mit dessen Hilfe ga- rantiert wird, dass ein Codestück (z.B. eine Methode) nur von einem Thread gleichzeitig betreten wird. Python (und damit Jython) verfügt von Hause aus nicht über ein gleichwer- tiges eingebautes Sprachmittel, daher gibt es in der Jython-Bibliothek das Modul synchronize. Die dort enthaltene Funktion make_synchronized() verpackt das übergebene aufrufbare Objekt in ein ansonsten unsichtbares Hilfsobjekt, das lediglich dessen Ausführung innerhalb eines Java-synchro- nized-Blocks sicherstellt. Da in Jython wie in Standard-Python Klassen-, Funktions- und Methoden- definitionen selbst Anweisungscharakter haben und damit tatsächlich "aus- geführt" werden, ist eine Zuweisung des mit make_synchronized erzeugten 3935042698_v01.book Seite 146 Freitag, 14. Oktober 2005 3:31 15
Jython 147 Hilfsobjekts an den ursprünglichen Methodennamen innerhalb der Klassen- definition ohne Probleme möglich. Bei der Definition der Klasse MeinPlayer wird die Methode controllerUpdate() also gleich nach ihrer Definition durch die synchronisierte Version ersetzt: Natürlich beschränken sich die Fähigkeiten des Java Media Framework nicht allein auf das Abspielen von Mediendaten. Das JMF stellt eine große Sammlung von Klassen zum Erzeugen, Analysieren und Umformen von Mediendaten zur Verfügung, die Jython-Nutzern somit auch offen steht. 4.1.5 Einbetten von Jython in Java-Programme Eine der häufig lobend erwähnten Eigenschaften des in C implementierten Standard-Pythoninterpreters ist, dass er sich problemlos in andere C-Pro- gramme einbetten und von diesen aus ansteuern lässt. In der Einleitung wur- de bereits erwähnt, dass eine solche eingebettete Skriptsprache die Anpass- barkeit und Flexibilität eines Softwaresystems deutlich steigern kann. Auch Jython lässt sich auf einfache Weise in eigene Java-Programme integrieren, wie nachfolgend gezeigt wird. In diesem Beispiel geht es um ein Java-Programm, das aus einer Reihe von Terminen den frühesten herausfinden soll. Intern benötigt das Java-Pro- gramm für jeden Termin eine Zeichenkette zur Beschreibung und -- wegen der besseren Sortierbarkeit -- die Datumsangabe als langer Integer im Form JJJJMMTT, also beispielsweise 20061224 für den 24.12.2006. Die Termin- darstellung in den Eingabedateien kann davon jedoch stark abweichen, wie das Beispiel in Listing 4.6 zeigt. class MeinPlayer(jm.ControllerListener): ... def controllerUpdate(self, event): ... controllerUpdate = synchronize.make_synchronized( controllerUpdate) 3935042698_v01.book Seite 147 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 148 Um das Programm jederzeit leicht um neue Datenformate und Datenquellen erweitern zu können, trennen wir den Aspekt des Einlesens und Konvertie- rens der Termindaten von dem der Verarbeitung, also hier des Findens des frühesten Termins. Den Datenimport und die Konvertierung übernimmt da- bei das eingebettete Jython, da die Programmiersprache gerade für solche Stringverarbeitungsaufgaben sehr geeignet ist, während der Verarbeitungs- teil dem in Java geschriebenen Hauptprogramm zufällt. Um die reibungslose Kommunikation zwischen den beiden Programmteilen sicherzustellen, legen wir für das Python-Importmodul in unserem Beispiel folgende Konventionen fest: 1. Es stellt eine Klasse Importer bereit, die als Konstruktorparameter eine Zeichenkette mit der Bezeichnung der zu lesenden Datenquelle akzep- tiert, also etwa einen Dateinamen oder eine URL. Diese Klasse Importer hat 2. eine parameterlose Methode lesen(), die jeweils den nächsten Termin als Python-Tupel im vom Java-Teil erwarteten Format (Termin als Ganz- zahl, Beschreibung als String) zurückgibt oder None, falls das Ende der Eingabedaten erreicht wurde, und 3. eine parameterlose Methode schliessen(), welche eventuelle Aufräum- arbeiten wie das Schließen einer Datei ermöglicht. Listing 4.7 zeigt eine mögliche Realisierung einer solchen Import-Klasse. Bei jedem Aufruf von lesen() wird eine Zeile aus der im Konstruktor mit- geteilten Termindatei gelesen, die Bestandteile werden mit split() nach den Trennzeichen (Doppelpunkt bzw. Leerzeichen) zerteilt und im benötig- ten Format als Tupel zurückgegeben. 1. Mai 2007: ausschlafen 15. August 2005: Weinfest in Wiesbaden 01. Januar 2005: Restfeuerwerk bei eBay versteigern 24. Dezember 2006: Geschenke kaufen Listing 4.6: Eingabedatei mit Terminen 3935042698_v01.book Seite 148 Freitag, 14. Oktober 2005 3:31 15
Jython 149 Die Einbettung des Jython-Interpreters in ein Java-Programm geschieht über die Klasse PythonInterpreter, die über eine sehr schlanke und über- sichtliche Schnittstelle Operationen wie beispielsweise das Laden und Ausführen von Python-Dateien, die Auswertung/Ausführung von Python-Ausdrücken oder Anweisun- gen, die als String übergeben werden und das Auslesen und Setzen von Variablen ermöglicht. MONATE = ["Januar","Februar","Maerz","April", "Mai", "Juni", "Juli", "August", "September","Oktober","November", "Dezember"] class Importer: def __init__(self, dateipfad): self.datei = open(dateipfad) def schliessen(self): self.datei.close() def lesen(self): zeile = self.datei.readline() if not zeile: return None (termin, b) = zeile.split(":") beschreibung = b.strip() (t, monatsname, j) = termin.split() tag = int(t.replace(".","")) monat = MONATE.index(monatsname) + 1 jahr = int(j) return 10000L*jahr+100*monat+tag, beschreibung Listing 4.7: TerminImporter.py 3935042698_v01.book Seite 149 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 150 Der Java-Teil der Terminfinder-Anwendung ist in Listing 4.8 zu sehen. Nach der Erzeugung des PythonInterpreter-Objekts python wird mit Hilfe der Methode execfile() das Python-Modul TerminImporter.py ausgeführt und damit die dort definierte Python-Klasse Importer im Interpreter bereit- gestellt. Mit der exec-Methode wird dann eine Importer-Instanz gebildet und als Jython-Variable mit dem Namen imp abgespeichert. Eine eventuell dabei auftretende Python-Exception (beispielsweise wegen eines falschen Dateinamens) wird mit Hilfe des normalen Java-Exceptionhandlings behan- delt. Der Inhalt der soeben belegten Python-Variable imp kann nun mit der Methode get() aus dem Jython-Interpreter herausgeholt und der Java-Vari- ablen importerObj zugewiesen werden. Nun lässt sich die Eingabedatei mit Hilfe des Importer-Objekts zeilenweise lesen. Die for-Schleife demonstriert dazu zwei verschiedene Möglichkei- ten. Im Initialisierungsausdruck wird über die Java-Variable importerObj mit invoke() die gewünschte Jython-Methode lesen() aufgerufen. Alterna- tiv kann -- wie im Aktualisierungsausdruck der Schleife -- der Methodenauf- ruf mit python.eval() als Python-Ausdruck in einer Zeichenkette ausgewer- tet werden. In beiden Fällen ist das Ergebnis ein PyObject. Da die Importer-Klasse ver- einbarungsgemäß im Erfolgsfall ein Python-Tupel und bei Erreichen des Dateiendes None zurückliefert, kann die Testmethode isSequenceType() von PyObject gut zur Realisierung der Schleifen-Abbruchbedingung eingesetzt werden. Innerhalb der Schleife ist daher klar, dass das PyObject nur ein Tupel sein kann. Um sinnvoll auf dessen Komponenten (Datum und Beschreibung ei- nes Termins) zugreifen zu können, wird das gelesene zeile-Objekt zum Typ PyTuple konkretisiert und einer passenden Variable tupel zugewiesen. Für den Zugriff auf die Datenstruktur können dadurch nun die gewohnten Py- thon-Methoden wie __getitem__ benutzt werden. Das Ziel ist damit erreicht, der Java-Programmteil enthält keine Abhängig- keiten vom Eingabedateiformat, er arbeitet allein auf den Daten, die er sich aufbereitet vom leicht anpassbaren Jython-Objekt abholt. 3935042698_v01.book Seite 150 Freitag, 14. Oktober 2005 3:31 15
Jython 151 import org.python.util.PythonInterpreter; import org.python.core.*; public class TerminFinder { public static void main(String[] args) { long minDatum = Long.MAX_VALUE; String minAktion = "(nichts zu tun)"; String impName = "TerminImporter.py"; String dateiName = "Termine.txt"; PythonInterpreter python=new PythonInterpreter(); python.execfile(impName); try { python.exec("imp = Importer('"+dateiName+"')"); } catch (PyException e) { System.err.println("Problem mit "+dateiName); e.printStackTrace(); System.exit(1); }PyObject importerObj = python.get("imp"); for (PyObject zeile=importerObj.invoke("lesen"); zeile.isSequenceType(); zeile = python.eval("imp.lesen()")) { PyTuple tupel = (PyTuple)zeile; long datum = Py.py2long(tupel.__getitem__(0)); if (datum < minDatum) { minDatum = datum; minAktion = tupel.__getitem__(1).toString(); } }python.exec("imp.schliessen()"); System.out.println("Erste Aktion am " + minDatum+":"+minAktion); } }Listing 4.8: TerminFinder.java 3935042698_v01.book Seite 151 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 152 Im Beispiel sind der Name des Jython-Moduls TerminImporter.py und der Dateiname Termine.txt in String-Variablen abgelegt, um anzudeuten, dass ein Wechsel oder sogar eine dynamische Auswahl des Import-Skript- namens, beispielsweise in Abhängigkeit vom Namen der zu verarbeitenden Eingabedatei, leicht zu realisieren ist. 4.1.6 Kompilieren von Jython-Skripten Neben der Möglichkeit, den Interpreter in eigene Java-Programme einzubet- ten und auf die oben gezeigte Weise auf Python-Objekte zuzugreifen, er- laubt Jython auch die direkte Übersetzung von Python-Klassen in Java class-Dateien. Die dabei erzeugten Java-Klassen dürfen dann wie gewohnt in eigenem Java-Code verwendet werden. Insbesondere können sie auch ohne Umwege als Basisklasse für neue Java-Klassen dienen, die Java-Klas- se erbt also von der ursprünglich in Python geschriebenen. Hierzu dient das Skript jythonc aus der Jython-Distribution. Es verfügt über eine größere Zahl von Kommandozeilenoptionen, die es beispielsweise ge- statten, einen gewünschten Java-Paketnamen für die zu übersetzenden Klas- sen vorzugeben oder automatisch nach abhängigen Modulen zu suchen, um diese gegebenenfalls bei der Erzeugung des resultierenden jar-Archivs ein- zuschließen. Da Java im Gegensatz zu Python recht genaue Typfestlegungen für Variab- len und Methodenergebnisse schon zum Übersetzungszeitpunkt verlangt, benötigt jythonc für seine Arbeit entsprechende Zusatzangaben. Dies ge- schieht konfliktfrei zur normalen Python-Syntax durch spezielle Doc- Strings, in denen die Methodensignatur, also der Methodenname und die Typung von Parametern und Ergebnis, nach einer Kennzeichnung durch die Zeichenkette "@sig" in Java-Notation mitgeteilt werden. Daneben fordert jythonc, dass die zu übersetzenden Python-Klassen von einer Java-Schnitt- stelle oder einer Java-Basisklasse abgeleitet sind und dass Klassen- und Modulname übereinstimmen. Leider gibt sich jythonc recht fehlerignorant -- manche Abweichungen von den Vorgaben führen statt zu einer Fehlermel- dung einfach zu einem unbrauchbaren Ergebnis. 3935042698_v01.book Seite 152 Freitag, 14. Oktober 2005 3:31 15
Jython 153 Ein Beispiel zur Verwendung von jythonc zeigt Listing 4.9. Die dort gezeig- te Klasse Mittler hat eine Methode hinzufuegen(), die eine ganze Zahl als Parameter erwartet, und eine parameterlose Methode arithMittel(), die das arithmethische Mittel der zuvor hinzugefügten Zahlen als float-Wert lie- fert. Um den Anforderungen von jythonc zu genügen, erbt Mittler zudem von java.lang.Object und ist in einer Datei Mittler.py abgelegt. Mit folgendem Kommando erzeugt jythonc aus der Python-Datei eine Jar- Datei Mittler.jar, welche die kompilierte Python-Klasse Mittler.py in ei- nem Java-Paket beispiel.mittler enthält: Das Ergebnis kann nun leicht in einem Java-Programm benutzt werden. Lis- ting 4.10 zeigt dazu ein kleines Beispiel, in welchem mit Hilfe der ursprüng- lich in Python geschriebenen Klasse Mittler das arithmetische Mittel von siebzehn Zufallszahlen zwischen eins und siebzehn ermittelt wird. import java class Mittler(java.lang.Object): def __init__(self): self.summe = 0.0 self.anzahl = 0 def hinzufuegen(self, m): """@sig public void hinzufuegen(int m)""" self.summe += m self.anzahl += 1 def arithMittel(self): """@sig public float arithMittel()""" if self.anzahl: return self.summe / self.anzahl else: raise ZeroDivisionError, "Keine Daten" Listing 4.9: Python-Modul Mittler.py zur Mittelwertberechnung jythonc --package beispiel.mittler --jar Mittler.jar Mittler.py 3935042698_v01.book Seite 153 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 154 4.1.7 Ausblick auf Jython 2.2 Im Januar 2005 hat die Python Software Foundation (PSF) Mittel bewilligt, um die Weiterentwicklung von Jython zu fördern. Im Juli 2005 erschien eine Testversion (2.2alpha1). Mit der fertigen Jython-Version 2.2 werden auch unter Jython weitere Sprachmittel verfügbar, die mittlerweile in Standard-Python Einzug gehal- ten haben, wie etwa Iteratoren, Generatoren und die "new-style classes". Daneben wird die Java-Integration weiter verbessert. So werden viele höhe- re Jython-Datentypen in ihrer internen Umsetzung auch die entsprechenden Java-Collection-Schnittstellen implementieren. Eine Python-Liste kann da- mit beispielsweise direkt einer Java-Methode übergeben werden, die ein Objekt mit der Schnittstelle java.util.List erwartet, wie etwa dem Kon- struktor von ArrayList: import beispiel.mittler.Mittler; import java.util.Random; public class PyCompiled { public static void main(String[] args) { Mittler m = new Mittler(); Random rnd = new Random(); for(inti=0;i<17;i++){ m.hinzufuegen(1+Math.abs(rnd.nextInt())%17); }System.out.println("Mittel: " + m.arithMittel()); } }Listing 4.10: Java-Hauptprogramm verwendet kompilierte Python-Klasse from java.util import ArrayList pythonList = [1,2,3] a = java.util.ArrayList(pythonList) 3935042698_v01.book Seite 154 Freitag, 14. Oktober 2005 3:31 15
JPype 155 4.2 JPype Jython erreicht seine sehr enge Integration mit Java durch eine vollständige Re-Implementierung der Programmiersprache Python für die JVM. Erwei- terungen des Python-Sprachumfangs werden dadurch aber erst mit einer ge- wissen Verzögerung für Jython verfügbar, und die Nutzung der vielen in C implementierten Python-Erweiterungen ist ebenfalls nicht möglich. Das relativ junge Projekt JPype verfolgt dagegen den Ansatz, den in C implementierten Standard-Python-Interpreter beizubehalten und ihn über das Java Native Interface (JNI) mit einer JVM kommunizieren zu lassen. Damit ist es bisher beispielsweise möglich, Java-Klassen zu instanzieren und auf Methoden und Instanzvariablen zuzugreifen. Auch das Registrieren von Python-Objekten zum Aufruf über eine Java-Schnittstelle kann über eine Hilfsklasse umgesetzt werden. Eine weitergehende Integration wie etwa das Erben einer Python-Klasse von einer Java-Basisklasse ist aller- dings (in der vorliegenden Version 0.5.1 vom Juli 2005) noch nicht möglich. JPype ist unter http://jpype.sourceforge.net erhältlich. Windows-Anwen- dern steht dort ein installationsfertiges Binärpaket zur Verfügung, Linux- Benutzern hilft ein setup-Skript in der Source-Distribution von JPype, die Übersetzung und Installation auf ihrem System vorzunehmen. Zum Vergleich mit Jython greifen wir in Listing 4.11 das zuvor schon ver- wendete Beispiel eines Java-Fensters mit Knopf auf, diesmal als Skript für Standard-Python und Zugriff auf die Java-Klassen mit JPype. Die Aufgabe wurde dahingehend leicht erweitert, dass das Programm nach dem dritten Klick auf den Knopf beendet werden soll. import jpype, threading jpype.startJVM(jpype.getDefaultJVMPath()) swing = jpype.javax.swing ende = threading.Event() Listing 4.11: Swing-Fenster mit Knopf, JPype-Version 3935042698_v01.book Seite 155 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 156 Zunächst fällt hier auf, dass JPype ein explizites Starten und Stoppen der Java Virtual Machine erfordert (startJVM() und shutdownJVM()). Beim Star- ten hilft die Funktion getDefaultJVM(), den vom startJVM() benötigten Pfad zur JVM zu ermitteln. Die Erzeugung von Java-Klassen und die Verwendung ihrer Methoden er- laubt JPype in gewohnter Weise. Sogar das Registrieren einer Python-Klas- se zur Reaktion auf das Anklicken des Knopfs ist mit der Hilfsklasse JProxy möglich. Instanzen dieser Klasse "implementieren" die im ersten Konstruk- torparameter benannte Java-Schnittstelle, indem sie Zugriffe auf deren Me- thoden an das im zweiten Parameter übergebene Python-Objekt delegieren. Ein solches JProxy-Objekt kann problemlos an eine Java-Methode überge- class KnopfListener: def __init__(self): self.zaehler = 3 def actionPerformed(self,event): self.zaehler -= 1 print "Klick! Noch",self.zaehler,"uebrig." if self.zaehler == 0: ende.set() fenster = swing.JFrame("Ein Fenster") fenster.setDefaultCloseOperation( swing.JFrame.DISPOSE_ON_CLOSE) knopf = swing.JButton("Klick mich!") fenster.getContentPane().add(knopf) knopf.addActionListener(jpype.JProxy( "java.awt.event.ActionListener", inst=KnopfListener())) fenster.pack() fenster.setVisible(True) ende.wait() fenster.dispose() jpype.shutdownJVM() Listing 4.11: Swing-Fenster mit Knopf, JPype-Version (Forts.) 3935042698_v01.book Seite 156 Freitag, 14. Oktober 2005 3:31 15
Kommunikation über XML-RPC 157 ben werden, welche eine Realisierung der angegebenen Schnittstelle als Pa- rameter erwartet. Im Beispiel ist dies die addActionListener-Methode des Knopfs. Im Gegensatz zum Jython-Beispiel, bei dem Skript und Swing-GUI als pa- rallele Threads auf derselben JVM liefen, beendet sich der Python-Interpre- ter gleich beim Erreichen des Skript-Endes. Er wartet also nicht, bis das von der parallel laufenden JVM erzeugte Fenster vom Benutzer geschlossen wird. Damit wäre aber auch die beim Knopf registrierte Instanz der Python- Klasse KnopfListener nicht mehr erreichbar. Das Python-Skript muss daher auf möglichst ressourcensparende Weise warten, bis es vom GUI die Erlaub- nis zur Fortsetzung signalisiert bekommt. Hier hilft die Event-Klasse aus dem Python-Standardmodul threading. Das Event-Objekt ende blockiert über die wait-Methode das Python-Skript so lange, bis es von der grafischen Oberfläche nach dem dritten Klick per ende.set() freigegeben wird und mit dem Aufräumen des Fensters und Be- enden der JVM fortfahren kann. 4.3 Kommunikation über XML-RPC XML-RPC ist ein Kommunikationsprotokoll, das einem Client-Prozess den Aufruf von Funktionen ermöglicht, die von einem Server-Prozess auf dem- selben oder einem anderen Rechner bereitgestellt werden. XML-RPC ermöglicht lediglich den Aufruf entfernter Funktionen, es hat keinen Objektbegriff wie beispielsweise dessen Weiterentwicklung SOAP. An einfachen Datentypen stehen ganze und Fließkommazahlen, Wahrheits- werte, Datums-/Zeitangaben, Zeichenketten sowie nach dem Base64-Ver- fahren codierte Binärdaten zur Verfügung, als strukturierte Datentypen wer- den Felder und Strukturen angeboten, die im Falle von Python auf Listen und Dictionaries abgebildet werden. Der Vorteil von XML-RPC liegt in seiner Einfachheit und der Verwendung verbreiteter Standards. So wird zur Kommunikation zwischen Client und Server das populäre Protokoll HTTP verwendet und die darüber ausge- tauschten Nachrichten sind einfach strukturierte XML-Textdokumente. Da sowohl HTTP als auch XML von modernen Programmiersprachen durch 3935042698_v01.book Seite 157 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 158 entsprechende Bibliotheksmodule direkt unterstützt werden, ist eine Imple- mentierung von XML-RPC recht einfach. Die Python-Standardbibliothek verfügt sogar schon länger über entspre- chende fertige Module und auch für Java sind verschiedene XML-RPC-Pa- kete verfügbar. In den folgenden Beispielen wird die Java-Implementierung aus dem Apache Webservices-Projekt verwendet, die unter http://ws.apa- che.org/xmlrpc erhältlich ist. Eine nützliche Liste benötigter Hilfspakete ist unter http://ws.apache.org/xmlrpc/dependencies.html aufgelistet. Die folgenden Beispiele zeigen, wie man in Python und in Java geschriebe- ne Softwarekomponenten über XML-RPC miteinander zusammenarbeiten lassen kann. 4.3.1 Zugriff auf XML-RPC-Dienste Wie bereits erwähnt dient XML-RPC insbesondere dazu, auf entfernten Rechnern angebotene Dienste einfach in eigene (Client-)Software integrie- ren zu können bzw. als Server selbst solche Dienste anzubieten. Ein Beispiel für einen solchen öffentlich zugänglichen Dienst ist Geoco- der.us, der zu einer gegebenen Postadresse in den USA weitere Angaben wie etwa die geografische Länge und Breite bestimmt. Client-Seite in Python Das Skript in Listing 4.12 zeigt, wie man den Dienst von Python aus nutzt, um die Koordinaten zur Adresse der Zope Corporation in Fredericksburg herauszufinden, wo die PythonLabs angesiedelt sind. Die eigentliche Abfrage des Servers besteht dabei gerade einmal aus zwei Zeilen, nämlich der Erzeugung des ServerProxy-Objekts server für die ge- wünschte Dienst-URL und dem nachfolgenden Aufruf der Methode geo- code(), welche eine Zeichenkette adresse als Parameter mitgegeben be- kommt und im Falle dieses Dienstes eine Liste als Ergebnis liefert. 3935042698_v01.book Seite 158 Freitag, 14. Oktober 2005 3:31 15
Kommunikation über XML-RPC 159 Dem Aufruf von geocode() selbst ist nicht anzusehen, dass hinter den Ku- lissen seine Parameter in XML umcodiert, über eine Netzwerkverbindung zum Zielrechner geschickt sowie dort für die serverseitig verwendete Pro- grammiersprache decodiert werden und dann ein Aufruf der gewünschten serverseitigen Funktion erfolgt, deren Ergebnis wieder in XML gewandelt zurückgeschickt wird, bevor es schließlich für unseren Gebrauch handlich in eine Python-Datenstruktur konvertiert als Ergebniswert unseres Funktions- aufrufs zur Verfügung steht. All dies geschieht für den Entwickler transpa- rent. Auch die gewohnte Fehlerbehandlung mit Exceptions ist möglich. Tritt bei der serverseitigen Ausführung der gewählten Funktion ein Fehler auf, so führt dies auf der Seite des aufrufenden Clients zu einer XML-RPC-Exception, die mit den üblichen Sprachmitteln abgefangen und behandelt werden kann. In der Beispielausgabe des Skripts (Listing 4.13) ist zu erkennen, dass die Ergebnisliste in unserem Fall ein Python-Dictionary mit eine Reihe von An- gaben enthält, auf die das Skript mit den üblichen Python-Operationen zu- greifen kann. import xmlrpclib, pprint adresse = """ 513 Prince Edward Street, Fredericksburg, VA""" server = xmlrpclib.ServerProxy("http://geocoder.us/service/xmlrpc") ergebnis = server.geocode(adresse) if ergebnis: print "Koordinaten von",adresse print "-- Breite %(lat).2f" % ergebnis[0] print "-- Länge %(long).2f" % ergebnis[0] print "Alle Angaben:" pprint.pprint(ergebnis) else:print "Adresse nicht gefunden" Listing 4.12: Python-Client für den Geocoder-Dienst 3935042698_v01.book Seite 159 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 160 Client-Seite in Java Die Lösung zur gleichen Aufgabe ist in Java erwartungsgemäß etwas länger (Listing 4.14), trotzdem ähneln die Schritte den zuvor besprochenen: 1. Es wird eine Instanz von XmlRpcClient erzeugt, welche dabei die URL des XML-RPC-Dienstes übergeben bekommt. 2. Der Aufruf der gewünschten Funktion wird durchgeführt. Dieser Schritt ist wegen der statischen Typung von Java deutlich aufwändiger als in Python. Die Parameterliste muss zunächst als Java-Vector zusammen- gebaut werden, bevor sie -- zusammen mit einer Zeichenkette mit dem Namen der gewünschten Funktion "geocode" -- als Parameter der execu- te-Methode des xmlrpc-Objekts übergeben werden kann. 3. Das Ergebnis des Aufrufs (im Programmiersprachen-unabhängigen Sinne von XML-RPC ein "Feld von Strukturen") wurde in Python als Liste von Dictionaries umgesetzt und entspricht in der hier verwendeten Java-Implementierung von XML-RPC einem Vektor von Java Hash- table-Objekten. Nach den entsprechenden Typangleichungen kann so- mit per get() auf die gewünschten Ergebniskomponenten zugegriffen werden. Koordinaten von 513 Prince Edward Street, Fredericksburg, VA -- Breite 38.30 -- Länge -77.46 Alle Angaben: [{'city': 'Fredericksburg', 'lat': 38.298754000000002, 'long': -77.460256000000001, 'number': 600, 'prefix': '', 'state': 'VA', 'street': 'Prince Edward', 'suffix': '', 'type': 'St', 'zip': 22401}] Listing 4.13: Ausgabe des Python-Client-Programms 3935042698_v01.book Seite 160 Freitag, 14. Oktober 2005 3:31 15
Kommunikation über XML-RPC 161 import java.io.IOException; import java.net.MalformedURLException; import java.util.*; import org.apache.xmlrpc.*; public class XmlRpcBeispielClient { public static void main(String[] args) { XmlRpcClient xmlrpc = null; Vector result = null; String adresse = "513 Prince Edward Street, \ Fredericksburg, VA"; try { xmlrpc = new XmlRpcClient( "http://geocoder.us/service/xmlrpc"); } catch (MalformedURLException e) { e.printStackTrace(); }Vector params = new Vector (); params.addElement (adresse); try { result = (Vector)xmlrpc.execute("geocode", params); } catch (XmlRpcException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }if (result != null && result.size() > 0) { Hashtable daten = (Hashtable)result.get(0); System.out.println("Koordinaten von "+adresse); System.out.println("-- Breite: " + daten.get("lat")); System.out.println("-- Länge: " + daten.get("long")); }else{ System.out.println("Adresse nicht gefunden"); } } }Listing 4.14: Java-Client für den Geocoder-Dienst 3935042698_v01.book Seite 161 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 162 4.3.2 Anbieten von XML-RPC-Diensten Neben der clientseitigen Nutzung bestehender XML-RPC-Dienste ist so- wohl in Python wie auch in Java die Erstellung eigener Dienstangebote -- also der Server-Seite -- leicht realisierbar. Server-Seite in Python Zum Anbieten eigener Funktionen über das Internet enthält die Python- Standardbibliothek das Modul SimpleXMLRPCServer. Im nachfolgenden Bei- spiel wird gezeigt, wie man mit dessen Hilfe das Addieren und Subtrahieren zweier Zahlen bereitstellen kann. Zur Realisierung des XML-RPC-Dienstes sind lediglich drei Schritte not- wendig (Listing 4.15): 1. Bei der Erstellung eines SimpleXMLRPCServer-Objekts wird ein Tupel übergeben, das Namen oder IP-Adresse des eigenen Rechners und die TCP-Portnummer, auf der der Dienst angeboten werden soll, enthält. Der Einfachheit halber wird im Beispiel in der ersten Tupelkomponente der Leerstring übergeben, wodurch der Dienst auf allen geeigneten Netz- werkschnittstellen des Rechners angeboten wird. 2. Dem so erzeugten Server-Objekt wird nun mit register_instance() ein Handler-Objekt zugeordnet, an das eingehende Aufrufe für die dort ent- haltenen Methoden durchgereicht werden. Das Hinzufügen weiterer Handler (Instanzen anderer Klassen oder auch einzelner Python-Funk- tionen) ist möglich. 3. Schließlich wird der Dienst mit serve_forever() gestartet. Er ist somit unter der zuvor gewählten Adresse und Portnummer von außen nutzbar. import SimpleXMLRPCServer class RechnerHandler: def addieren(self,x,y): returnx+y def subtrahieren(self,x,y): return x-y ... Listing 4.15: XML-RPC-Server in Python 3935042698_v01.book Seite 162 Freitag, 14. Oktober 2005 3:31 15
Kommunikation über XML-RPC 163 Das Testen des Dienstes lässt sich gut von der Python-Kommandozeile aus erledigen. Falls der Test dabei auf einem anderen Rechner vorgenommen wird als dem, auf dem der Dienst läuft, müsste lediglich in der Dienst-URL der Name "localhost" durch den Namen oder die IP-Adresse des Server- Rechners ersetzt werden. Server-Seite in Java Auch in Java besteht die Realisierung der Server-Seite aus zwei Teilen: einer Handler-Klasse RechnerHandler (Listing 4.16), welche die nach außen an- zubietenden Methoden beinhaltet und in diesem Beispiel dem Python- Gegenstück sehr ähnlich ist, und der Hauptklasse XmlRpcRechnerServer (Listing 4.17). serv = SimpleXMLRPCServer.SimpleXMLRPCServer( ("",8080) ) serv.register_instance(RechnerHandler()) serv.serve_forever() >>> import xmlrpclib >>> s = xmlrpclib.ServerProxy("http://localhost:8080") >>> s.addieren(17, s.subtrahieren(20,16)) 21 package beispiel.xmlrpcrechner.server; public class RechnerHandler { public int addieren(int x, int y) { return x + y; }public int subtrahieren(int x, int y) { return x - y; } }Listing 4.16: RechnerHandler.java, XML-RPC-Handler in Java Listing 4.15: XML-RPC-Server in Python (Forts.) 3935042698_v01.book Seite 163 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 164 Die Hauptklasse erzeugt eine Instanz von WebServer mit der gewünschten TCP-Portnummer (hier: 8080) und registriert dort eine Instanz der Handler- Klasse. Die addHandler-Methode ermöglicht dabei in ihrem ersten Parame- ter die Angabe eines Namens, der beim Aufruf der Funktionen dieses Hand- lers verwendet wird. Dies fördert die Übersichtlichkeit, sollten mehrere ver- schiedene Handler-Objekte angeboten werden. Auch für den Test des Java-Servers bietet sich wieder die bewährte Python- Kommandozeile an. Zu beachten ist hierbei, dass der beim Registrieren der Handlerklasse angegebene Name rechner beim Aufruf der gewünschten Funktion voranzustellen ist, wie das Beispiel zeigt: package beispiel.xmlrpcrechner.server; import org.apache.xmlrpc.*; public class XmlRpcRechnerServer { WebServer server = null; public void run() { server = new WebServer(8080); server.addHandler("rechner", new RechnerHandler()); server.setParanoid(false); server.start(); } public static void main(String[] args) { XmlRpcRechnerServer service = new XmlRpcRechnerServer(); service.run(); } }Listing 4.17: XmlRpcRechnerServer.java, XML-RPC-Server in Java >>> import xmlrpclib >>> s=xmlrpclib.ServerProxy("http://localhost:8080") >>> s.rechner.addieren(1,2) 3 3935042698_v01.book Seite 164 Freitag, 14. Oktober 2005 3:31 15
Welche Alternative ist nun die beste? 165 4.4 Welche Alternative ist nun die beste? Jede der drei ausgewählten Möglichkeiten zur Verbindung von Python und Java hat ihre Stärken und Schwächen, so dass sich die Frage nach der besten -- wenn überhaupt -- nur für einen konkreten Anwendungszweck beantwor- ten lässt. Jython bietet als vollwertiger Bewohner der Java-Plattform die engste Inte- gration der drei vorgestellten Alternativen, einschließlich Vererbung von Java nach Python (und umgekehrt), Kompilierbarkeit in Java Byte-Code, komfortabler Unterstützung spezieller Java-Features wie Properties und Event-Handler und vieles mehr. Mit der anstehenden Version 2.2 wird die Nutzung von Jython-Datentypen mit Java-Methoden noch weiter verbessert. Durch das Einbetten des Jython-Interpreters können Java-Programme in Hinblick auf Anpassbarkeit und Erweiterbarkeit deutlich profitieren -- nicht zuletzt deswegen nutzen in letzter Zeit immer mehr namhafte Anbieter kom- merzieller Java-Softwaresysteme diese Möglichkeit. Nachteilig sind die mitunter längere Verzögerung, bis neue Sprachmittel von Standard-Python Eingang in Jython finden, und die im Vergleich zur C-Implementierung schlechtere Ausführungsgeschwindigkeit. Das noch recht neue JPype versucht, durch die Verbindung des Standard- Python-Interpreters mit der Java Virtual Machine die reichhaltigen Biblio- theken beider Plattformen gleichzeitig nutzbar zu machen. Die Integration in Richtung Java ist dabei (noch?) nicht so vollständig wie bei Jython, dafür läuft der Python-Code mit der vollen Geschwindigkeit von Standard-Python und profitiert unmittelbar von der Weiterentwicklung des Interpreters und in C geschriebener Erweiterungsmodule. Bezüglich des Aufwands bei Soft- wareverteilung und -installation ist jedoch zu berücksichtigen, dass JPype- Programme zum Ablauf zwei Laufzeitumgebungen benötigten (die von Python und eine JVM), während sich Jython-Code (einschließlich des Inter- preters) in eine Java Jar-Datei verpacken und damit recht einfach verteilen lässt. Mit XML-RPC schließlich verfügt man über eine einfach umzusetzende und von der Python-Standardbibliothek direkt unterstützte Möglichkeit, Soft- warekomponenten, die neben Python und Java auch in anderen Program- miersprachen geschrieben sein können und vielleicht auf mehrere Rechner 3935042698_v01.book Seite 165 Freitag, 14. Oktober 2005 3:31 15
4 -- Python und die Java-Welt 166 mit unterschiedlichen Betriebssystemen verteilt sind, miteinander kooperie- ren zu lassen. Diesem Vorteil gegenüber stehen Einschränkungen bezüglich der verwendbaren Datentypen, die mangelnde Objektorientierung und der im Vergleich zu den anderen beiden Alternativen durch die XML-(De-)Co- dierung und Netzwerkkommunikation hohe Aufwand pro Funktionsaufruf. 3935042698_v01.book Seite 166 Freitag, 14. Oktober 2005 3:31 15
167 Stichwortverzeichnis <page>-Element 120 <prefs>-Tag 119 <script>-Tag 117 <title>-Element 121 @sig 152 __future__ 34 __init__() 65, 89 __iter__() 24, 86 __new__() 89 A abgeschirmte Umgebung 102 Action-Handler 104, 116 ActionListener 138 ADL 101 Aggregat 76 Aggregatbildung 79 Alpha-Kanal 108, 110, 111 anchor 103, 114 Applet 99 Applet Description Language 101 Applet-Beschreibungssprache 101 argumentlose yield-Ausdrücke 51 Array 105 Attributnamen 116 auflösungsunabhängige Einheiten 113 auflösungsunabhängige Layouts 112 Aufräumen 131 Aufspannen 114 Aufzählungen 122 Ausnahmen 17 Auswahldialog für Dateien 124 Auswahldialog für Schriften 123 B Basisklasse 131 bg-color 108, 110 bg-uri 108 Bilddatei 109 Bilder 109 bind 120, 131 bind-Attribut 119 bind-Methode 127 Blockvorlagen 53 boolesche Werte 121 Breite des Elements 104 C C# 2.0 13 callback 118, 120, 131 Callback-Funktion 120, 127, 131 CDATA-Block 117 chain(*iterables) 35 Checkbox 121 class 58 color 109, 110 Container-Element 103, 105 Continuations 15 Control-Basisklasse 131 Control-Inspector 125 controllerUpdate() 145 Controls 102, 125, 127, 128 createRealizedPlayer() 145 D Deckkraft 109, 111 Desktop 99 Desktop-Applet 99 desktop-borders 106 Desktop-Ränder 106 Digitaluhr 128 digits 122 dom 108 Drag&Drop 104 Drop-down-Menü 122 Dsp-Namensraum 117 DTP Punkt 113 duck typing 25 3935042698_v01.book Seite 167 Freitag, 14. Oktober 2005 3:31 15
Stichwortverzeichnis 168 E Eingebettete Python-Skripte 101 Eingebettete Skripte 116 Einheiten 124 einheitenbasierte Positionierung 112 enabled 120 Entitäten 117 eric3 14 escaping continuations 17 eval() 150 Event-Attribut 116 Event-Objekt 104, 116, 157 execfile() 150 F Farben 122 Farbnamen 111 Farbwerte 111 Fensterattribute 106 Fenstertitel 106 Fibonacci-Zahlen 26 fill 107 font 109, 110 Fortschrittsbalken 107 funktionales Programmieren 37 Funktionsplotter 110 G Ganzzahlen 123 Gargabe Collector 21 gDesklet 99 gDesklets-Shell 100, 125 Generatorausdrücke 30 Generatorinstanz 21 Generatorklassen 38 Gleitkommawerte 122 Graph 76 Graphen zeichnen 110 graphics 108 Greenlets 20 Größenangaben 118 groupby(sequence, keyfunc) 36 GUI-Programmierung 40 H Haskell 30 height 104 Hello-World-Beispiel 102 help 120 Hilfetext 120 Hintergrund 118 Hintergrundfarbe 108, 110 Hintergrundgrafik 108 Höhe des Elements 104 HTML 111 IIcon 13, 106 id 103, 117, 120 Inch 113 Initialisierung 65 Inline-Scripts 101 Installation 100 interaktive Applets 104 Interface 125 Interface-ID 125 Introspektion 90 invoke() 150 IPython 14 isSequenceType() 150 iter() 27 Iteratoren 86 Iterator-Protokoll 24 iterierbare Klassen 86 itertools 34 JJava Media Framework 140 JMF 140 JProxy 156 JPype 155 jythonc 152 K Kind-Element 103 Klasse 58 Klassenattribute 65 3935042698_v01.book Seite 168 Freitag, 14. Oktober 2005 3:31 15
Stichwortverzeichnis 169 Konfigurationsdialoge 119 Konfigurationsoption 119, 120 Konfigurationswidgets 120 Kontextwechsel 17, 50 kooperatives Multitasking-Modell 43 Koordinaten 118 Koordinateneinheiten 113 Koroutinen 15 L label 120, 123 Layout 105, 112 lazy evaluation 30 length 105 List-Comprehensions 30 Listener 136 M Magische Methoden 68 make_synchronized() 146 Maske 106 Maßeinheit 111 max 122 Mehrfachvererbung 72 mehrzeiliger Text 110 Metaklassen 93 Method Resolution Order 72, 75 Methoden 68 Methodendekorator 44 Microthreads 41 min 122 Mini-DOM 108 Minuszeichen 116 MRO 75 N Nachkommastellen 122 New-Style-Klassen 59 next() 25, 86 nicht-lokale Programmsteuerung 18 O object 59 Objektattribute 65 Old-Style-Klassen 59 on-click 104 on-doubleclick 104 on-enter 104 on-file-drop 104 on-key-press 104 on-key-release 104 on-leave 104 opacity 109 orientation 107 P Package 130 passives (push-basiertes) Verfahren 127 Pi nach Leibniz 33 Pixelformate 109 Pixelkoordinaten 113 Positionierungsverankerung 103, 114 Positionsverankerung 112 Prefs-Namensraum 124 Private Namen 80 privilegierter Code 125 Properties 81 Prozent 118 prozentuale Positionierung 112 prozentualer Anteil 113 py.lib 28, 48 PyCrust 14 PyObject 150 PyQt 40 Python Enhancement Pro posals 51 Python-Direktmodus 132 PythonInterpreter 149 Python-Skripte 116 R Rahmendicke 107 Rahmenelemente 107 register_instance() 162 Rekursive Generatoren 49 3935042698_v01.book Seite 169 Freitag, 14. Oktober 2005 3:31 15
Stichwortverzeichnis 170 relative Positionierung 103, 112, 115 relative-to 103, 115 reservierte Zeichen 117 return 23 RGBA 111 RGB-Werte 111 Ruby 13 SSandbox 102 scale 109 scale-bidir 110 scale-holdmax 110 Scheme 19 Schnittstelle 125, 127, 128 Schnittstellenbezeichner 125, 132 Schnittstellendefinition 128 Schnittstellenklasse 128 Schriftart 109, 110 self-Referenz 116 ServerProxy 158 shape 106 Shell-Kommando 118 Sichtbarkeit 80 Sichtbarkeitszustand 103 SimpleXMLRPCServer 162 size 110 Skalierungsfaktor 109 Skripte 116, 117 Stackframe 16, 21 Statische Methoden 85 Status 120 StopIteration 23, 87 strict evaluation 30 Superkonstruktor 129 SVG 108 SVG-Code 108 SVG-Grafik 108 synchronize 146 T takewhile(predicate, iterable) 35 tee(iterable, n) 35 Testen 61 Testskript 131 Text 110, 120 Textfarbe 109, 110 threading 157 Timer 118, 131 title 106 Tooltip 120 Transparenz 108, 110 Transparenzeffekte 111 type 95 type() 58 U Überladen 69 Überschrift 121 Umbruch 110 UML-Klassendiagramm 63, 71 undefiniert 118 Unit 118 Unterstrich 116 uri 108, 109 urllib2 41 V value 109, 110, 111, 123 Verankerung 114 visible 103 Vordefinierte Funktionen 118 W walk() 141 WebServer 164 Wertebereich 110 width 104 window-flags 106 wrap-at 110 Wurzel element 103 Wurzel-Element 106 X x 103 x-Koordinate 103 3935042698_v01.book Seite 170 Freitag, 14. Oktober 2005 3:31 15
Stichwortverzeichnis 171 XML-Parser 103 XML-RPC 157 XmlRpcClient 160 Y y 103 yield 20 y-Koordinate 103 Z Zeichenkodierung 103 Zentimeter 113, 118 Zoll 113, 118 Zugriffskontrolle 81 Zugriffsrechte 128 3935042698_v01.book Seite 171 Freitag, 14. Oktober 2005 3:31 15
3935042698_v01.book Seite 172 Freitag, 14. Oktober 2005 3:31 15
173 Die Autoren Markus Nix Markus Nix, geboren 1972, verheiratet und Vater von drei Kindern, hat Li- teraturwissenschaft, Philosophie und Geschichte in Frankfurt am Main stu- diert. Seit 1997 ist er Web Application Developer und Consultant. Gegen- wärtig arbeitet er als Entwickler für die Mayflower GmbH in Würzburg und an seiner Promotionsschrift zum Thema Hypertext. Daneben veröffentlicht er regelmäßig Fachartikel in IT-Zeitschriften wie PHP Magazin, Objekt- spektrum, Linux Enterprise und Content Management Magazin. Markus Nix ist Mitglied im Programmkomitee des Linuxtages e.V. und engagiert sich im Rahmen der Organisationen AIIM, Oasis, UPA und Oscom. Martin Grimme Diplom-Informatiker Martin Grimme, geboren 1979, gründete nach seinem Studium an der Universität Passau zusammen mit einem Bekannten das Startup-Unternehmen lintegra, wo er als CTO tätig ist. Den ersten Kontakt mit Python hatte er im Frühjahr 2000 und ist seitdem der Sprache verfallen. Nach der Veröffentlichung kleinerer Programme und Tutorials arbeitet er seit 2003 an dem Desktop-Applet-System gDesklets und hält auf Veranstal- tungen im In- und Ausland Vorträge darüber. Nebenher ist er hin und wieder als Freelancer für den Linux-Distributor und gDesklets-Sponsor VidaLinux tätig. Sie erreichen Martin Grimme unter exploring.python@pycage.de Torsten Marek Torsten Marek studiert Computerlinguistik und arbeitet seit mehr als vier Jahren mit Python. Lag sein Schwerpunkt bisher auf GUI-Programmierung mit PyQt, so nutzte er seinen Beitrag zu diesem Buch, um sich mehr mit dem theoretischen Hintergrund auseinander zu setzen. Seine Gründe, warum er Python zu seiner Hauptprogrammiersprache erkoren hat, sind neben Pythons gutem Design seine große Menge an zur Verfügung stehenden Bibliotheken, die einfache Erweiterbarkeit mit C und C++ sowie die gut funktionierende Community. Sie erreichen Torsten Marek unter torsten.marek@gmx.net. 3935042698_v01.book Seite 173 Freitag, 14. Oktober 2005 3:31 15
Die Autoren 174 Michael Weigend Michael Weigend ist Diplom-Informatiker und Lehrer an einer Wittener Schule. Seit 1995 hat er einen Lehrauftrag für Didaktik der Informatik an der FernUniversität Hagen. Er ist Verfasser zahlreicher Publikationen zum Computereinsatz in der Schule und zur Informatik, darunter eine Python- Sprachreferenz (Python Ge-Packt, 2. Aufl., mitp, Bonn 2005) und ein Lehr- buch (Objektorientierte Programmierung mit Python, 2. Aufl., mitp, Bonn 2005). Auf Python stieß Michael Weigend im Jahre 2000, als er an der Uni- versität Dortmund eine Projektgruppe leitete, die Online-Studienmaterial zur Einführung in die Informatik entwickelte. Sie erreichen Michael Wei- gend unter: michael.weigend@fernuni-hagen.de Wolfgang Weitz Wolfgang Weitz ist seit 2003 Professor am Fachbereich Informatik der FH Wiesbaden und bietet u.a. Lehrveranstaltungen aus dem Bereich Software- technik und Rechnernetze im Studiengang Medieninformatik an, wo Python für alle Studierenden zum Pflichtprogramm gehört. Der Erstkontakt mit Py- thon fand um 1994 über die Arbeit mit dem verteilten Objekt-Framework ILU statt. Seitdem benutzt er Python in verschiedenen Forschungs- und In- dustrie-Projekten für so ziemlich alles, wofür es nicht nachweislich unge- eignet ist, und versucht, seine Umwelt zu überzeugen, dies auch zu tun. 3935042698_v01.book Seite 174 Freitag, 14. Oktober 2005 3:31 15