/
Author: Nix M. Grimme M. Marek T. Weigend M. Weitz W.
Tags: software python programmierung programmiersprache python
ISBN: 3-935042-69-8
Year: 2005
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. < 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