This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Open Source Software wird gegenüber kommerziellen Lösungen immer wichtiger. Addison-Wesley trägt dieser Entwicklung Rechnung mit den Büchern der Open Source Library. Administratoren, Entwickler und User erhalten hier professionelles Know-how, um freie Software effizient einzusetzen. Behandelt werden sowohl Themen wie Betriebssysteme, Netzwerke und Sicherheit als auch Programmierung. Eine Auswahl aus unserem Programm: Hibernate hat sich innerhalb kurzer Zeit zum De-Facto-Standard unter den Object-Relational-Mappern (OR-Mapper) entwickelt. Die mit Hibernate entwickelten Konzepte und deren gelungene Umsetzung haben maßgeblich das Java Persistence API innerhalb EJB 3.0 bestimmt. Es ist nun erstmals möglich, sowohl in Java SE als auch Java EE mit denselben Persistenzmechanismen zu arbeiten. Die Autoren führen anhand eines praxisnahen Beispiels in die Konzepte des OR-Mappings und deren Umsetzung mithilfe von Hibernate ein. Anfänger werden mithilfe von zunächst einfachen Beispielen an die Thematik herangeführt. Mit zunehmender Komplexität der Beispiele werden auch erfahrene Software-Entwickler eine Reihe neuer Informationen aus dem Bereich der ObjektPersistenz mitnehmen können. Die Integration von Hibernate in komplexe Anwendungen, insbesondere solche mit web-basierten Architekturen, sowie die Darstellung der neuesten Entwicklungen in diesem Bereich runden das Buch ab. Java-Persistence-API mit Hibernate Bernd Müller, Harald Wehr 360 S. Euro 39,95 (D), 41,10 (A) ISBN 978-3-8273-2537-2
Python ist eine moderne, interpretierte, interaktive und objektorientierte Skriptsprache, die sowohl unter Unix (inklusive Linux) als auch Windows einsetzbar und für die meisten gängigen Betriebssysteme frei erhältlich ist. In diesem Video-Training erläutert Ihnen der Trainer die Grundlagen der Skriptsprache Python und geht schrittweise auf die Stärken der Sprache ein. Auf diese Weise werden insbesondere Programmierer und Systemadministratoren angesprochen. Im Anschluss daran wird die eigentliche Funktionsweise der Programmiersprache erklärt.
Python Peter Walerowski Euro 49,95 (D), 51,40 (A) ISBN 978-3-8273-6080-9
Peter Walerowski
Grundlagen und Praxis
An imprint of Pearson Education München • Boston • San Francisco • Harlow, England Don Mills, Ontario • Sydney • Mexico City Madrid • Amsterdam
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar. Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig.
Fast alle Hardware- und Softwarebezeichnungen und weitere Stichworte und sonstige Angaben, die in diesem Buch verwendet werden, sind als eingetragene Marken geschützt. Da es nicht möglich ist, in allen Fällen zeitnah zu ermitteln, ob ein Markenschutz besteht, wird das ® Symbol in diesem Buch nicht verwendet.
Umwelthinweis: Dieses Produkt wurde auf chlorfrei gebleichtem Papier gedruckt. Um Rohstoffe zu sparen, haben wir auf Folienverpackung verzichtet.
Einleitung Python-Programme kann man lesen! Klingt seltsam, trifft aber einen der wesentlichen Pluspunkte dieser Sprache ziemlich genau. Es gibt sicherlich guten und es gibt schlechten Programmierstil. Hat man einen Quelltext vor sich, den man nicht selbst geschrieben hat, wird man, auch wenn er in gutem Stil geschrieben ist, nicht mit den Kommentaren allein auskommen, um ihn zu verstehen. In vielen Fällen ist es notwendig, die Programmzeilen und Anweisungsblöcke selbst zu lesen. Die meisten Sprachen machen es einem da nicht leicht. Seien es die Namen von Standardfunktionen oder eine Flut von Klammern und anderen Zeichen, die die Dateien füllen – man muss den Quelltext selbst lesen und braucht oft enorm viel Zeit, um die Strukturen zu durchschauen. Python-Quelltexte erfreuen sich dagegen einer bestechenden Schlichtheit, die es einem ermöglicht, sehr schnell und direkt die Programmstruktur zu entschlüsseln. Das liegt an Pythons Syntax, die ohne Klammern für Befehlsblöcke auskommt, keine Anweisungsendzeichen verlangt und immer dort Einrückungen hat, wo sie wirklich verlangt sind. Und das ist keinesfalls übertrieben, denn die Einrückungen machen den Befehlsblock deutlich und sind damit notwendig, um ein lauffähiges Skript zu schreiben. Das liegt aber auch daran, dass man mit Python für eine Funktionalität meistens deutlich weniger Quelltext braucht als in anderen Sprachen. Wo andere zehn Zeilen benötigen, braucht Python nur fünf oder nur drei. Python ist eher minimalistisch. Die Zahl der reservierten Wörter ist gering, was den Leistungsumfang aber keineswegs schmälert. Vielmehr sind die Basissprachkonstrukte auf das Notwendigste reduziert. Wenn man häufig mit verschiedenen Sprachen umgehen muss, ist man immer wieder verblüfft, wie einfach und klar sich in Python so manches regeln lässt, und fragt sich, warum andere nicht schon längst darauf gekommen sind. Aber bei allem Lob, es geht um ein wenig mehr: Wenn man eine Sprache schneller erfassen kann als andere und wenn man mit dieser Sprache vergleichsweise weniger Aufwand für das gleiche Ergebnis hat, erreicht man in kürzerer Zeit das gesteckte Ziel. Wenn daneben andere meinen Code schnell verstehen können und ich den von anderen, dann wird die Arbeit im Team dadurch effizienter. Und hier werden Pythons Vorteile zu echten Kostenvorteilen.
Einleitung
Es ist wie mit einem guten Werkzeug: Es ist sicherlich teurer in der Anschaffung, aber die Arbeit gelingt damit besser, so dass sich die Investition schnell amortisiert. Aber der Vergleich hinkt. Python ist in der Anschaffung nicht teurer als andere. Es ist eine frei verfügbare Sprache. Die genannten Vorzüge von Python haben die Sprache auch zu einer der beliebtesten Sprachen unter Informatiklehrern gemacht. Die Einstiegshürde ist gering und man erzielt schnell Ergebnisse. So lässt sich wilden Kids das Programmieren leichter beibringen. Ganz nebenbei gehen die Schüler dann auch noch mit einer Sprache um, die konsequent objektorientiert ist. Alle Datentypen sind Objekte und selbst wenn man nur prozedural programmiert, man arbeitet eigentlich doch immer mit Objekten. Python schafft auf diese Weise etwas, was kaum eine andere Sprache erreicht: Programmieren wird für Personenkreise zugänglich, denen dieses Feld sonst zu komplex war. Davon profitieren auch die anderen Sprachen, denn wer erst einmal Gefallen an der Programmierung durch und mit Python gefunden hat, wird sich auch andere Sprachen früher oder später einmal anschauen. Man darf und sollte bei aller Simplizität, die Python mitbringt, aber nicht glauben, dass diese Sprache ein Spielzeug wäre. Schaut man sich einmal an, wer Python einsetzt und welche Software auf Python basiert, wird man schnell sehen, dass man es mit einem effizienten und professionellen Werkzeug zu tun hat. Die Python-Webseite gibt hier einen beeindruckenden Überblick (http://www.python.org/about/success/). Aber es wird auch in weniger sichtbaren Bereichen eingesetzt. In der Steuerung von Linux-Rechnern mit Shell-Skripten steht Python heute gleichberechtigt neben Perl. Die sehr erfolgreiche Ubuntu-Distribution nimmt hier eine Vorreiterrolle ein. Python wird sehr viel im wissenschaftlichen Bereich eingesetzt, um spezielle Programme für die Auswertung von Forschungsdaten zu entwickeln. Eine unbekannte Zahl von Websites nutzen die unterschiedlichen python-basierten Webframeworks wie Django, Turbo Gears oder Zope, – um nur die großen zu nennen. (Im Anhang finden sich Links zu weiteren Frameworks.) Und nicht zuletzt verwendet Google für seine vielfältigen Applikationen und Dienste bevorzugt Python. Der Python-Schöpfer Guido von Rossum arbeit derzeit übrigens für Google. Dieses Buch behandelt deshalb auch ein weites Spektrum der Einsatzmöglichkeiten von Python. Nach einer grundlegenden Einführung in die Sprache wird gezeigt, wie man Texte verarbeitet und mit Dateien und dem Dateisystem umgeht. Es wird die Entwicklung eigener Klassen dargelegt und das Erstellen von grafischen Benutzeroberflächen erläutert. Ein Kapitel widmet sich der Be- und Verarbeitung von Bilddateien und nicht zuletzt wird das Programmieren für das Internet mit Python beschrieben. Eine Reihe von praktischen Beispielen zeigen die genauen Vorgehensweisen. Diese Beispiele und jegliche Software, die zum Durcharbeiten notwendig ist, finden sich auf der beiliegenden CD. Sie können also direkt loslegen und selbst erfahren, dass es einfach Spaß macht, mit Python zu arbeiten.
14
Einleitung
Mein Dank Es gibt eine große Menge an Wissen in der Python-Community, von dem ich profitiert habe und weiterhin profitiere. Dieses Buch ist auch Ausdruck meines Danks an die Community, der ich damit etwas zurückgeben möchte. Ferner geht mein Dank an Andreas Herold für die Korrekturen und das Hineinschlüpfen in die Rolle des Lesers sowie an Manuel Herzog für die Erstellung astreiner Grafiken. Für die kritischen und konstruktiven Kommentare danke ich Daniel Koch. Das Buch hat davon deutlich profitiert. Und nicht zuletzt will ich denen danken, die mir während der Arbeit an diesem Buch den Rücken freigehalten haben und mich leider viel zu selten gesehen haben.
15
1 1.1
Den Rechner für die Arbeit mit Python einrichten Python installieren
Bei einem Linux-Rechner kann man je nach Distribution das Glück haben, bereits mit Python arbeiten zu können. Wie Perl gehört es in der Linux-Welt vielfach zur Grundausstattung des Rechners, so z. B. bei Debian und Ubuntu. Und auch Macintosh-Rechner mit MacOS X haben Python bereits installiert (wenn auch meistens nicht in der aktuellsten Version). Aber die Nutzer anderer Betriebssysteme können beruhigt sein – die Zeit, die die Installation von Python benötigt, reicht für eine Kaffeepause nicht aus. Für jede Plattform findet man die entsprechende Python-Version unter http://www. python.org/download/. Wer eine Installation aus den Quellen bevorzugt, kann einen der beiden zur Verfügung stehenden Tarballs herunterladen. Unter Linux kann natürlich auch die Paketverwaltung der jeweiligen Distribution verwendet werden, um Python zu installieren. Allerdings dauert es mitunter eine Weile, bis die aktuellste Version in die Distribution aufgenommen wird. Für die Windows-Plattform bietet die Download-Seite Installationsprogramme an. Es gibt jeweils eines für Rechner mit x86-Prozessoren sowie für 64-Bit-Architekturen von AMD und Intel (Itanium). Schließlich kann auch für MacOS X ein Installer heruntergeladen werden, der sowohl für PPC- als auch Intel-Macs Verwendung findet. Mit ihm lässt sich Python auf dem MacOS auf den aktuellsten Stand bringen. Es sind hier aber die Systemvoraussetzungen zu beachten (siehe http://www.python.org/download /mac/). Python 2.5 benötigt wenigstens die MacOS X Version 10.3. Neben den bereits genannten Plattformen unterstützt Python noch viele weitere Plattformen wie z. B. BeOs, Solaris, AIX oder HP-UX. Auf diesen Plattformen ist eine Installation aus den Quellen anzuraten. Die kanadische Firma ActiveState bietet spezielle Versionen für einige dieser Plattformen an (http://aspn.activestate.com/ASPN/ Downloads/ActivePython). Diese stehen aber nicht unter einer Open-Source-Lizenz. Wer eine (eventuell auch ältere) Version für eine spezielle Plattform benötigt, wird auf der Internetseite http://www.python.org/download/other/ wahrscheinlich fündig. Von Nokia S60 über Psion und Playstation bis MorphOS sind hier einige Plattformen aufgelistet.
1 Den Rechner für die Arbeit mit Python einrichten
Ein Wort noch an Nutzer von älteren Windows- und Macintosh-Systemen: Mit der Python-Version 2.5 wurde die Unterstützung für MacOS 9 eingestellt. Das gleiche Schicksal wird Windows 98 und Windows ME mit der Version 2.6 ereilen. Es wird zu aufwändig, systemspezifischen Code weiterhin mitzupflegen. Immerhin: Python hat diese Plattformen länger unterstützt als deren eigene Herstellerfirmen.
1.1.1
Installation unter Windows
Bei der Installation unter Windows muss man lediglich den Anweisungen des Installationsprogramms folgen. Man kann entscheiden, ob Python für alle oder nur den aktuell angemeldeten Benutzer installiert werden soll. Dies hat Auswirkungen auf die Einträge im Startmenü. Danach legt man fest, in welches Verzeichnis Python installiert wird. Der Installer schlägt vor, direkt unter C: ein Verzeichnis anzulegen, und es spricht nichts dagegen, diesem Vorschlag zu folgen. Python lässt sich aber auch in ein anderes Verzeichnis oder in eine andere Partition installieren. Schließlich kann man einzelne Bestandteile des Pakets von der Installation ausschließen, etwa die Module Tcl/Tk, wenn man sich sicher ist, dass man keine Programme mit grafischer Oberfläche entwickeln wird. Ist die Auswahl getroffen, wird Python anschließend in das gewählte Verzeichnis installiert. Dieser Vorgang dauert nur wenige Momente, dann kann Python verwendet werden.
1.1.2
Installation unter Linux
Wenn die gewählte Linux-Distribution Python nicht schon standardmäßig installiert hat, ist es am einfachsten und sichersten, die Installation mit dem Paketmanager des Systems vorzunehmen. Um zu überprüfen, ob Python installiert ist, kann man in der Shell einfach ~ python -V
eingeben. Es sollte dann eine Ausgabe erfolgen, die die aktuelle Python-Version anzeigt: Python 2.5
Erhält man die Ausgabe command not found
ist Python nicht installiert. Wer immer mit der allerneuesten Python-Version arbeiten will, greift zu den Quellen und kompiliert sich »sein« Python selbst. Hierzu ist es zunächst notwendig, den heruntergeladenen Tarball zu entpacken. Im Verzeichnis, in dem der sich der Tarball befindet, gibt man ~ tar -xvzf Python-2.5.1.tgz
18
1.2 Python interaktiv verwenden
ein. Jetzt kann man einige Ausgaben in der Shell verfolgen. Sind diese an ihrem Ende angelangt, wechselt man in das erzeugte Verzeichnis Python-2.5.1 mit ~ cd Python-2.5.1
Die Installation erfolgt mit ~./configure ~ make ~ make install.
Python befindet sich nunmehr im Verzeichnis /usr/local. In der Shell kann jetzt der Befehl python eingegeben werden und der Interpreter meldet sich anschließend mit der Ausgabe der Python-Version und einem Prompt zur Eingabe von Python-Befehlen. Dieser besteht aus drei aufeinanderfolgenden spitzen Klammern: >>>
1.2
Python interaktiv verwenden
Während also Python unter Linux in der Shell bzw. unter MacOS in einem Terminal direkt verwendet werden kann, ist dies unter Windows in der Eingabeaufforderung nicht ohne Weiteres möglich. Python bringt aber eine eigene Eingabeaufforderung mit, die man im Startmenü unter Python 2.5/Python (command-line) aufrufen kann. Man erhält in diesem Fenster ebenfalls den Prompt zur direkten Eingabe von PythonBefehlen. Windows lässt sich aber auch dazu bewegen, Python in der eigenen Eingabeaufforderung auszuführen. Dazu muss man den Pfad zum Python-Verzeichnis in die Systemvariable Path eintragen. Man wählt dazu im Start-Menü EINSTELLUNGEN/SYSTEMSTEUERUNG/SYSTEM. In dem daraufhin erscheinenden Fenster geht man auf den Reiter ERWEITERT. Dort befindet sich der Button UMGEBUNGSVARIABLEN, der bei Klick ein weiteres Fenster öffnet. In dessen unterem Bereich kann man die Systemvariable Path auswählen und bearbeiten. Ein neuer Eintrag muss mit einem Semikolon von den bestehenden abgetrennt werden. Für Python muss der Eintrag ; C:\Python25\
angefügt werden. Anschließend werden alle bisher geöffneten Fenster mit OK geschlossen. Jetzt kann wie unter Linux in der Eingabeaufforderung der Befehl python eingegeben werden. Man erhält nun auch hier anschließend den Python Prompt. Es ist an der Zeit, die erste Zeile Python zu schreiben, und das soll ganz traditionell eine »Hallo Welt«-Begrüßung sein: >>> print 'Hallo Welt'
19
1 Den Rechner für die Arbeit mit Python einrichten
Damit der Befehl ausgeführt werden kann, muss nur noch die (¢)-Taste gedrückt werden. Man sieht direkt das Ergebnis des Befehls und erhält in der folgenden Zeile einen neuen Prompt (siehe folgende Abbildung). Diese Verwendung von Python wird als interaktiv bezeichnet.
Abbildung 1.1:
Python über die Windows-Eingabeaufforderung
1.3
Python IDLE
1.3.1
Grundlegende Funktionsweise
Python interaktiv zu verwenden, ist immer sinnvoll, wenn man schnell etwas testen will, z. B. einen einzelnen Befehl. Sobald man aber wiederverwendbaren Code entwickeln möchte, der in einzelnen Modulen organisiert ist, muss man natürlich Dateien anlegen und speichern können. Python bietet dafür eine Entwicklungsumgebung mit dem Namen IDLE (Integrated Development Environment). Dieses Programm, das komplett in Python geschrieben ist, eignet sich gut für kleinere bis mittlere Projekte. Die Entwicklungsumgebung IDLE befindet sich unter Windows im Startmenü in der Python-Programmgruppe. Unter Linux und MacOS X findet man die Python-IDLE im Unterverzeichnis Lib/idlelib des Python-Installationsverzeichnisses. Dort findet sich das Skript idle.py, das ausgeführt werden muss, um die IDLE zu starten. Man erhält eine Python-Shell, in der man ebenfalls Python-Befehle direkt ausführen kann (Abbildung 1.2). Das ist noch kein wirklicher Fortschritt gegenüber dem interaktiven Ausführen von Python. Aber die Python-Shell bietet Syntaxhervorhebung, so dass der eingegebene Code besser lesbar ist. Tatsächlich hat die Shell auch hier die Aufgabe, Testumgebung zu sein, in der einzelne Befehle, aber auch komplette Skripte ausgeführt werden können. Über das Menü SHELL hat man mit dem Befehl RESTART die Möglichkeit, alle definierten Variablen zu löschen, so dass man wieder mit einer »frischen« Shell arbeiten kann.
20
1.3 Python IDLE
Abbildung 1.2:
Das Shell-Fenster der Python-IDLE
Neben der Shell gibt es aber die Möglichkeit, weitere Fenster zu öffnen, in denen Skripte geschrieben und bearbeitet werden können. Ein solches Fenster erhält man über das Menü FILE – NEW WINDOW oder mit der Tastenkombination (Strg) + (N). Es öffnet sich zusätzlich zur Shell (Abbildung 1.3).
Abbildung 1.3:
Das Skript-Fenster der Python-IDLE
21
1 Den Rechner für die Arbeit mit Python einrichten
Auch dieses Fenster zeichnet sich durch Syntaxhervorhebung sowie automatische Einrückung aus. Um einen ersten Test mit diesem Fenster zu machen, bietet sich ein 'Hallo Welt' an, das mal in einer anderen Form – nämlich mit einer for-Schleife – ausgegeben wird. Folgender Code ist dafür einzugeben: for buchstabe in ['H','a','l','l','o',' ','W','e','l','t']: print buchstabe,
Nun kann man das Skript auf der Festplatte des Rechners speichern. Dafür stehen die Befehle SAVE oder SAVE AS... im Menü FILE zur Verfügung (über die Tastatur mit (Strg) + (S)). Nachdem man das Skript gespeichert hat, kann es ausgeführt werden. Dazu wählt man im Menü RUN den Befehl Run Module ((F5)). Das Shell-Fenster wird daraufhin in den Vordergrund gebracht und das Ergebnis des Skriptablaufs erscheint darin. Bevor ein Skript ausgeführt wird, wird es auf korrekte Syntax geprüft. Ermittelt diese Prüfung einen Fehler, kommt es nicht zur Ausführung, sondern es wird eine Fehlermeldung ausgegeben. Diese syntaktische Prüfung kann auch separat durchgeführt werden – über den Menübefehl RUN – CHECK MODULE ((Alt) + (X)). Diese Überprüfung erkennt nur Syntaxfehler. Laufzeitfehler – also Fehler, die erst beim Ausführen eines Skripts auftreten – können so nicht aufgespürt werden. Diese werden in der Shell ausgegeben und haben folgende Form: Traceback (most recent call last): File "", line 1, in <module> zug.getSeats() File "G:\Projekte\Python-DVD\Beispiele\oop\Train4.py", line 13, in getSeats return self.__seat AttributeError: 'Train' object has no attribute '_Train__seat'
Man erhält sehr detaillierte Angaben darüber, wo der Fehler aufgetreten ist. Dies ist nicht unbedingt immer die Stelle, an der sich die Fehlerursache befindet, sondern die Zeile im Programmablauf, die den Fehler verursacht. Es wird der Pfad zur PythonDatei und die Zeilennummer ausgegeben. Darüber hinaus wird die Fehlerart und eine nähere Beschreibung ausgegeben. All das sind wertvolle Hinweise für das Debugging. Logische Fehler können allerdings so nicht entdeckt werden. Dafür stellt IDLE einen Debugger zur Verfügung (siehe Abschnitt 1.3.3).
1.3.2
Class Browser und Path Browser
Für komplexere Programmierungen bietet die IDLE zwei hilfreiche Tools, mit denen man sich einen guten Überblick verschaffen kann. Mit dem Klassenbrowser (Menü FILE – CLASS BROWSER, Tastatur: (Alt) + (C)) erhält man eine Ansicht aller Klassen und ihrer Methoden einer Python-Datei in einem Strukturbaum (Abbildung 1.4).
22
1.3 Python IDLE
Abbildung 1.4:
Der Klassenbrowser
Mit einem Doppelklick auf den Namen einer Methode ruft man das zugehörige Skriptfenster auf, in dem dann die Kopfzeile der Methode markiert wird. Dieses Tool eignet sich also hervorragend, um in einem komplexeren Modul zwischen Methoden zu navigieren.
Abbildung 1.5:
Der Path Browser
23
1 Den Rechner für die Arbeit mit Python einrichten
In einer ähnlichen Weise funktioniert der Path Browser (Menü FILE – PATH BROWSER). Er ermöglicht das Durchsuchen aller Module, die sich im Python-Pfad befinden. Das sind zunächst alle Module, die sich innerhalb des Verzeichnisses der Python-Installation befinden (Abbildung 1.5). Ordner entsprechen hier realen Verzeichnissen im Dateisystem. Jedes Schlangen-Icon repräsentiert eine Python-Datei. Per Doppelklick lässt sich eine Datei im Skriptfenster öffnen. Das ist sehr hilfreich, wenn man ein Modul importieren muss und einen näheren Blick auf dessen Klassen und Methoden benötigt.
1.3.3
Der Debugger
Für die Fehlersuche stellt IDLE einen Debugger zur Verfügung, mit dem sich der Ablauf eines Skripts kontrollieren lässt. Der Aufruf erfolgt über das Menü DEBUG – DEBUGGER im Shell-Fenster. Man erhält ein Kontrollfenster, das es ermöglicht, die Werte von Variablen und Attributen im Skript-Ablauf zu verfolgen (Abbildung 1.6).
Abbildung 1.6:
Das Debug-Fenster
Mit dem Aufruf des Debuggers wird die Skriptausführung in den Debug-Modus geschaltet. Wenn man nun ein Skript ausführt, geschieht in der Shell zunächst nichts. Im Debug-Fenster aber erscheint ein Eintrag und die Buttons werden aktiv. Der Eintrag zeigt an, in welcher Zeile des Skripts sich die Ausführung momentan befindet. Über die Buttons GO und STEP kann die Ausführung in Gang gesetzt werden. Dabei ist vor allem STEP interessant, weil damit die Ausführung zeilenweise erfolgt, was die Möglichkeit bietet, jeden »Arbeitsschritt« des Skripts zu verfolgen. Im unteren Bereich des Fensters werden die aktuellen Werte aller definierten Variablen und Attribute angezeigt.
24
1.3 Python IDLE
Kommt es also zu einem unerwartetem Ergebnis bei einem Skriptablauf, hat man mit dem Debugger ein Tool zur Verfügung, das eine detaillierte Überprüfung des Ablaufs ermöglicht. Mit dem Schließen des Debug-Fensters wird auch der Debug-Modus wieder verlassen.
1.3.4
Konfiguration
Über OPTIONS – CONFIGURE IDLE ... lässt sich die Umgebung den eigenen Bedürfnissen entsprechend anpassen (Abbildung 1.7).
Abbildung 1.7:
Das Konfigurationsfenster
Einstellbar sind die Schrift und das Farbschema der Syntaxhervorhebung sowie die Anzahl der Leerzeichen, die für die Codeeinrückung verwendet werden. Alle Tastaturkürzel sind änderbar und es lässt sich bestimmen, dass die IDLE nicht mit dem Shell-Fenster, sondern direkt mit einem leeren Skriptfenster startet. Da ein PythonProjekt in der Regel aus einer Mehrzahl von Skripten besteht, bietet es sich an, die IDLE mit dem Skriptfenster starten zu lassen. Das Shellfenster kann dann bei Bedarf zusätzlich geöffnet werden.
25
1 Den Rechner für die Arbeit mit Python einrichten
1.4
Python mit Eclipse entwickeln
Eclipse ist eine sehr leistungsfähige und beliebte Entwicklungsumgebung aus der Java-Welt. Sie ist modular aufgebaut und kann über Plug-ins ihre Leistungsmerkmale erweitern. Sie ist selbst in Java geschrieben und kann deshalb plattformunabhängig verwendet werden, vorausgesetzt Java (JRE) ist auf dem Zielrechner installiert. Hinweis Die aktuelle Version der Java Runtime Environment kann auf der Webseite http://java.com/de/download/index.jsp heruntergeladen werden.
Für den Umgang mit Python stehen zwei Erweiterungen zur Verfügung: das DLTKFramework (Dynamic Language Toolkit), das neben Python auch andere Sprachen für Eclipse verfügbar macht, und PyDev (Python Development Environment), das speziell für Python entwickelt wurde. Letztere ist unter Python-Entwicklern die weitaus beliebtere Erweiterung.
1.4.1
Eclipse und PyDev installieren
Das Eclipse SDK – aktuell ist im Moment die Version 3.2.2 – kann unter der Adresse http://www.eclipse.org/downloads/ von der Programm-Homepage heruntergeladen werden. Für die Arbeit mit Python ist das Paket Eclipse Classic notwendig. Zusätzlich kann man ein deutsches Sprachpaket unter der Adresse http://download.eclipse.org/eclipse/ downloads/drops/L-3.2.1_Language_Packs-200609210945/index.php herunterladen. Es wird das Zip-Archiv NLpack1-eclipse-SDK-3.2.1-win32.zip benötigt. Nach dem Download von Eclipse hat man ein Zip-Archiv mit dem Namen eclipseSDK-3.2.2-win32.zip auf dem eigenen Rechner, das nur entpackt werden muss. Ein Installationsprogramm ist in dem Paket nicht enthalten, vielmehr kann man das Programm eclipse.exe direkt starten. Verknüpfungen auf den Desktop oder einen Eintrag in das Startmenü muss man selbst anlegen. Das Sprachpaket muss ebenfalls entpackt werden. In dem entpackten Verzeichnis, das den Namen eclipse trägt, befinden sich die Verzeichnisse features und plugins. Es muss nun der gesamte Inhalt von features in das gleichnamige Verzeichnis im Eclipse-Programmverzeichnis kopiert werden. Mit dem Inhalt von plugins wird genauso verfahren. Startet man nun Eclipse neu, erscheint die Programmoberfläche in deutscher Sprache. Die Installation von PyDev übernimmt die Update-Verwaltung von Eclipse. Diese ist über das Menü HILFE – SOFTWARE-UPDATES – SUCHEN UND INSTALLIEREN zu erreichen. Es öffnet sich ein Assistent, in dessen erster Ansicht man wählen muss, ob man
26
1.4 Python mit Eclipse entwickeln
Updates zu bestehenden oder neue Features installieren will. Nach der Wahl von NEUE FEATURES SUCHEN erhält man eine Ansicht, in der man die Download-Seite von PyDev eintragen kann. Dazu klickt man auf den Button NEUE FERNE SITE und trägt in das erscheinende Fenster die Adresse http://pydev.sourceforge.net/updates/ ein und bestätigt mit OK (Abbildung 1.8).
Abbildung 1.8:
PyDev installieren mit der Update-Verwaltung von Eclipse
Nun befindet sich ein Eintrag für PyDev in der Liste der Features, der bereits ausgewählt ist. Nach einem Klick auf FERTIG STELLEN startet Eclipse die Suche nach PyDev. Ist diese erfolgreich, erscheint eine neue Ansicht, in der man die zu installierenden Komponenten auswählen kann (Abbildung 1.9). Hier muss man die Mylar-Integration abwählen, die nur dann installiert werden kann, wenn das Plug-in Mylar bereits installiert ist. Mylar ist eine Erweiterung, die aufgabenorientiertes Arbeiten in Eclipse ermöglicht. Für die Zwecke in diesem Buch wird sie nicht benötigt. Wer aber aufgabenorientiert arbeiten muss oder will, sollte sich Mylar einmal ansehen. Es bietet eine vereinfachte Oberfläche, die das Arbeiten in dieser Form effektiver macht.
27
1 Den Rechner für die Arbeit mit Python einrichten
Hat man die Komponenten gewählt, folgen in der nächsten Ansicht die Lizenzbestimmungen des Plug-ins, die man akzeptieren muss. Danach öffnet sich ein weiteres Fenster, in dem man den Ordner für die Installation bestimmen kann. Plug-ins werden in den gleichnamigen Ordner im Eclipse-Programmverzeichnis installiert. Dieses Verzeichnis ist bereits voreingestellt. Nach einem Klick auf FERTIG STELLEN beginnt nun der Download des Plug-ins. Wenn dieser abgeschlossen ist, erhält man eine letzte Ansicht, in der man bestätigt, dass das heruntergeladene Plug-in nun auch installiert werden soll. Ist dieser Vorgang beendet, muss man nur noch Eclipse neu starten, damit PyDev zur Verfügung steht.
Abbildung 1.9:
1.4.2
Auswahl der PyDev-Komponenten
Eclipse und PyDev einrichten
Damit man nun mit dem PyDev-Plug-in arbeiten kann, muss man noch einige Einstellungen in Eclipse vornehmen. Das Einstellungsfenster von Eclipse ruft man unter FENSTER – BENUTZERVORGABEN... auf. Dieses Fenster listet in der linken Spalte die Einstellungsmöglichkeiten in Themenblöcken geordnet auf. Hier hat auch PyDev einen eigenen Block. Als Erstes muss PyDev mitgeteilt werden, wo sich der Python-Interpreter befindet. Dazu klickt man in der Ansicht PYTHON-INTERPRETER auf NEU und sucht in dem sich öffnenden Dialog das Verzeichnis der Python-Installation und darin das Interpreter-
28
1.4 Python mit Eclipse entwickeln
programm. Es folgt ein Fenster, in dem man auswählt, welche Verzeichnisse zum Python-Pfad hinzugefügt werden sollen. Hier kann man die Voreinstellung übernehmen. Ist alles korrekt eingestellt worden, sind in den drei Feldern der Ansicht nun die Einträge zu sehen (Abbildung 1.10).
Abbildung 1.10: Auswahl des Python-Interpreters Weitere Möglichkeiten zur Einstellung der Arbeitsumgebung finden sich unter Code Completition, Task Tags, Typing und in der Hauptansicht Pydev. Task Tags sind Auszeichnungen, die man in den Code notieren kann und die in der Oberfläche als Aufgaben angezeigt werden. Damit ist es möglich, Notizen im Code in einem eigenen Fenster anzeigen zu lassen. PyDev gibt die Tags TODO: und FIXME: vor. Weitere Tags können mit Leerzeichen getrennt eingegeben werden. Unter Typing kann man eine Reihe von Einstellungen vornehmen, die die Codeeinrückung und automatisches Einfügen bestimmter Codeelemente wie z. B. Klammern betreffen. Die Codekomplettierung, die bei der Arbeit Python-Code zur Eingabe vorschlägt, wird unter Code Completition gesteuert.
29
1 Den Rechner für die Arbeit mit Python einrichten
In der Hauptansicht Pydev kann man die Tabulatorlänge einstellen und die Farben der Syntaxhervorhebung festlegen. Natürlich beeinflussen auch einige der Einstellungen außerhalb des Pydev-Bereichs die Arbeit mit Python. Es würde hier aber zu weit führen, alle Einstellungen von Eclipse zu besprechen. Tipp Einen Einführungsworkshop zu Eclipse findet man unter http://www. admin-wissen.de/?id=212.
1.4.3
Die Oberfläche von PyDev
Da Eclipse ursprünglich für die Java-Entwicklung geschrieben wurde, ist die Programmoberfläche mit allen Fenstern (in Eclipse Perspektive genannt) darauf ausgelegt. Es ist aber sehr leicht, die Perspektive zu wechseln. Im rechten oberen Bereich des Programmfensters wird die aktuelle Perspektive angezeigt. Dort ist im Moment Java auf einem gedrückten Button zu finden. Links daneben befindet sich die Schaltfläche zum Hinzufügen einer neuen Perspektive. In dem aufklappenden Menü muss der Eintrag ANDERE ... gewählt werden, woraufhin sich ein Fenster öffnet, in dem die PyDev-Perspektive gewählt werden kann (Abbildung 1.11). Mit der Wahl ändern sich die Programmoberfläche mit allen Teilfenstern (in Eclipse Sicht genannt) und Schaltflächen, aber auch die Menüeinträge. Jetzt kann man mit der Python-Arbeit in Eclipse beginnen. Wenn man möchte, kann man noch die Java-Perspektive schließen, so dass ihre Schaltfläche aus der Oberfläche verschwindet. Dazu klickt man diese mit der rechten Maustaste an und wählt in dem erscheinenden Menü SCHLIESSEN. Jetzt ist die PyDev die alleinige Perspektive, die auch beim nächsten Start von Eclipse sofort erscheint. Mit Eclipse werden keine alleinigen Skriptdateien angelegt, sondern diese benötigen immer ein Projekt. Dieses legt man über DATEI – NEU – PYDEV PROJEKT an. Man erhält ein Fenster, in dem man dem Projekt einen Namen gibt und den Projekttyp festlegt. Hier sollte python 2.5 gewählt werden. Es kann auch ein eigener Arbeitsbereich für das Projekt festgelegt werden. Nach dem Anlegen des Projekts wird es in der rechten Sicht der Oberfläche (dem Package Explorer) angezeigt. Das Projekt besteht zunächst aus einem Verzeichnis mit dem Projektnamen und darin ein weiteres mit dem Namen src. In dieses kann man nun seine Skript-Dateien anlegen. Der Package Explorer funktioniert im Wesentlichen wie ein Datei-Explorer.
30
1.4 Python mit Eclipse entwickeln
Abbildung 1.11: Perspektivenwahl in Eclipse Zum Anlegen eines neuen Skripts klickt man mit der rechten Maustaste auf das Verzeichnis src. Man erhält ein Kontextmenü, in dem man über NEU – PYDEV MODULE eine neue Datei anlegt. In dem erscheinenden Fenster gibt man der Datei einen Namen und bestätigt mit FERTIG STELLEN. Es öffnet sich ein leeres Editorfenster in der Mitte der Oberfläche und im Package Explorer ist die Datei eingetragen (Abbildung 1.12). Jetzt ist es an der Zeit auch hier einen Gruß an die Welt zu erstellen. Dieses mal soll es eine kleine Funktion sein, die folgenden Code hat: def grusswort(): for num in [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]: print chr(num), grusswort()
Ist der Code eingegeben und gespeichert, kann man das Skript über das Menü AUSFÜHREN – AUSFÜHREN ALS – PYTHON RUN ((F5)) ausführen lassen. Das Ergebnis wird im unteren Fenster der Oberfläche angezeigt (Abbildung 1.13). Dort wurde automatisch die Konsole geöffnet, die für die Ausgabe zuständig ist. Im rechten Bereich der Oberfläche, in der Sicht Gliederung sieht man nun den Namen der Funktion eingetragen. Diese Sicht listet Klassen, Methoden und Funktionen, die ein Skript hat, auf und kann zur Navigation innerhalb eines Skripts verwendet werden.
31
1 Den Rechner für die Arbeit mit Python einrichten
Abbildung 1.12: Die PyDev-Perspektive mit geöffneter Datei
Abbildung 1.13: Ergebnis eines ausgeführten Skripts
32
1.4 Python mit Eclipse entwickeln
1.4.4
Debugging in Eclipse
Natürlich verfügt auch Eclipse über einen Debugger, der ebenfalls von PyDev genutzt werden kann. Der Debugger ist eine eigene Perspektive, die dann erscheint, wenn man in einem Skript einen Unterbrechungspunkt (Breakpoint) gesetzt hat. Dazu klickt man doppelt in die graue Leiste links neben der gewünschten Zeile. Der Breakpoint wird durch einen grünen Punkt gekennzeichnet. Über AUSFÜHREN – DEBUG FÜR ZULETZT GESTARTETES OBJEKT ((F11)) wird nun die Debugging-Perspektive geöffnet (Abbildung 1.13). Gleichzeitig wird die Ausführung des Skripts in Gang gesetzt, bis zum Breakpoint im Skript. Hier pausiert die Ausführung und man kann über die drei gelben Pfeile in der Sicht Debug die Ausführung schrittweise steuern. Die Sicht Variablen ermöglicht die Kontrolle, welche Werte die Variablen im weiteren Ablauf annehmen.
Abbildung 1.14: Die Debugger-Perspektive
33
2 2.1
Skripte schreiben und ausführen Syntaxregeln
Python ist sicherlich auch deshalb so beliebt, weil es eine einfache Syntax besitzt, die das Schreiben des Programmtextes beschleunigt. Auf alles Unwesentliche wird verzichtet und guter Programmierstil findet Unterstützung. Es ist in der Regel ohne große Mühen möglich, auch längere Skripte zu lesen und – vor allem auch – zu verstehen. Das kommt der Arbeit in Teams entgegen, in denen man häufig in und mit »fremdem Code« arbeiten muss.
2.1.1
Zeilennotation
Wie in anderen Programmiersprachen auch, besteht ein Python-Skript aus einer Folge von Anweisungen. Normalerweise wird in jeder Zeile des Skripts genau eine Anweisung notiert. Ein besonderes Zeichen zum Abschluss einer Anweisung wird in Python nicht benötigt. Das Zeilenende selbst markiert das Ende der Anweisung: print 'Hallo Welt'
Es ist daher nicht möglich, Folgendes zu schreiben: print 'Hallo Welt'
Dies würde bei der Ausführung des Skripts einen Fehler auslösen. Python kennt aber durchaus ein Zeichen, das das Ende einer Anweisung markiert. Es ist wie in vielen anderen Sprachen auch das Semikolon. Wenn man mehrere Anweisungen in eine Zeile schreiben will, kann man mit diesem Zeichen das Anweisungsende kenntlich machen: text = 'Hallo Welt'; print text
2 Skripte schreiben und ausführen
Dies ist allerdings schlechter Programmierstil, weil die Lesbarkeit des Programmcodes deutlich verschlechtert wird. Und da Python-Programmierer in der Regel einen guten Stil pflegen, wird man kaum einmal ein Python-Skript finden, in dem zwei oder gar mehr Anweisungen in eine Zeile geschrieben wurden. Wird eine Zeile einmal zu lang, kann man sie mit dem Backslash (\) mit der folgenden Zeile verbinden: print 'hallo \ Welt'
Man muss allerdings in der Zeile mit dem Backslash auf einen Kommentar (siehe Abschnitt 2.1.3)verzichten, weil das Kommentarzeichen (#) ebenfalls eine Anweisung beendet. Ebenfalls über mehrere Zeilen und ohne besonderes Kennzeichen kann man in Python geklammerte Ausdrücke notieren. Dabei ist es unerheblich, ob es ein Ausdruck in runden, eckigen oder geschweiften Klammern ist. Insbesondere bei Ausdrücken mit sehr vielen Elementen in der Klammer kann die Notierung über mehrere Zeilen für die Lesbarkeit des Codes von Vorteil sein. Am meisten profitiert die Lesbarkeit bei Dictionarys (siehe Abschnitt 3.2.4) von der Schreibweise über mehrere Zeilen, da deren Elemente aus Schlüssel-Werte-Paaren bestehen, die durch einen Doppelpunkt getrennt werden. Schreibt man bei einem längeren Dictionary jedes Element in eine eigene Zeile, erhält man sehr übersichtlichen Code: tag = ['Morgen':'6:00h', 'Mittag':'12:00h', 'Nachmittag':'15:00h', 'Abend':'18:00', 'Nacht':'22:00']
Solche Ausdrücke können zusätzlich in jeder Zeile Kommentare enthalten, weil kein explizites Zeichen die Weiterführung der Zeile kennzeichnet.
2.1.2
Anweisungsblöcke durch Einrückung
Zum guten Programmierstil gehört es auch, Anweisungsblöcke einzurücken. In Python hat man keine Wahl, man muss den Block einrücken, um ihn als solchen kenntlich zu machen. Wer das zu streng findet, der darf (muss) zum Ausgleich auf (geschweifte) Klammern zur Kennzeichnung von Blöcken verzichten. Mit anderen Worten: In Python werden Anweisungsblöcke durch Einrückungen kenntlich gemacht. Am Ende kommt dabei ein Quelltext heraus, der klar strukturiert und dadurch sehr übersichtlich ist: for item in ['Morgen', 'Mittag', 'Nachmittag','Abend', 'Nacht']: print item print 'Der ganze Tag.'
36
2.1 Syntaxregeln
In der ersten Zeile wird eine Schleife eingeleitet. Die folgende Zeile wird eingerückt, weil sie zum Schleifenkörper gehört. Sie wird für jedes Element in der Liste der Tageszeiten ausgeführt. Die dritte Zeile gehört nicht mehr zum Schleifenkörper und wird folglich nur einmal ausgeführt. Einrückungen erfolgen durch Tabs oder Leerzeichen. Mit einem Tab erledigt man eine Einrückung mit nur einem Tastendruck. Wie weit die Einrückung dann geht, kann in Eclipse, IDLE und vielen Texteditoren eingestellt werden. Häufig findet man eine Einrückungstiefe von zwei oder vier Leerzeichen pro Tab. Man muss darauf achten, dass die Einrückungstiefe innerhalb eines Blocks konstant ist, da es sonst entweder zu Syntax- oder logischen Fehlern kommt. IDLE und Eclipse führen automatische Einrückungen aus, wenn eine Zeile mit einem Doppelpunkt abgeschlossen wurde. Für die Ausrückung nach dem Ende eines Blocks muss man allerdings selbst sorgen. Tipp Wenn man im Team arbeitet und Code austauschen will, ist es sinnvoll, die Einrückungen mit Leerzeichen zu machen und sich auf eine einheitliche Einrückungstiefe zu einigen. Es kann sonst passieren, dass der Code fehlerhaft wird, weil man mit unterschiedlichen Einrückungstiefen gearbeitet hat. In Eclipse und vielen anderen Editoren kann man neben der Tabulatorweite auch einstellen, dass diese durch Leerzeichen ersetzt werden. So kann man Einrückungen weiterhin mit der Tabulatortaste schnell erledigen, bekommt aber Leerzeichen in den Quelltext geschrieben.
2.1.3
Kommentare
Auch wenn Python-Code in der Regel gut zu lesen ist, sind Kommentare im Programmtext für die Verständlichkeit notwendig. Jeder, der einmal ein eigenes Programm ohne Kommentare nach nur zwei Wochen wieder bearbeitet hat, weiß, dass man mit Kommentaren viel Zeit und Nerven spart. In Python werden Kommentarzeilen mit dem Rautezeichen (#) eingeleitet. Mit dem Zeilenende ist auch die Kommentarzeile beendet, die folgende Zeile wird wieder als Code interpretiert. Ein Zeichen für mehrzeilige Kommentare existiert nicht. # Berechnung der Fakultät von 5 n = 1 for i in [1,2,3,4,5]: n = n*i print n
37
2 Skripte schreiben und ausführen
Es ist auch möglich, Kommentare ans Ende einer Befehlszeile zu setzen, um kurze Hinweise zu geben. Auch solche Kommentare werden mit # begonnen: # Berechnung der Fakultät von 5 n = 1 # Anfangswert for i in [1,2,3,4,5]: n = n*i print n
Wie bereits in Abschnitt 2.1.1 erwähnt, können auch in Klammerausdrücken Kommentare eingefügt werden, wenn diese über mehrere Zeilen notiert werden: tag = ['Morgen', 'Mittag', 'Nachmittag', 'Abend', 'Nacht' ]
# # # # #
6:00 12:00 15:00 18:00 22:00
h h h h h
Dies ist eine gute Möglichkeit, um z. B. Parameter einer Funktion näher zu erläutern.
2.1.4
Regeln für Namen
Gibt man in der Python-Shell einen Ausdruck ein wie den folgenden >>> 'hallo'
so hat man zwar ein voll funktionierendes Zeichenkettenobjekt, aber es ist anonym. Anonyme Objekte lassen sich nicht aufrufen, sondern können nur direkt verarbeitet werden. In Verbindung mit einem Namen wird aus dem anonymen Objekt ein »bekanntes« Objekt, das über seinen Namen aufgerufen und wiederverwendet werden kann. >>> wort1 = 'hallo ' >>> print wort1 hallo >>> wort2 = 'Welt!' >>> print wort1+wort2 hallo Welt!
Genau genommen wird durch die Verwendung eines Namens eine Variable gebildet, die einen Wert, in diesem Fall eine Zeichenkette, speichert. Die Wiederverwendbarkeit wird also durch die Speicherung (im Arbeitsspeicher) ermöglicht. Die Bildung solcher Namen unterliegt in Python bestimmten Regeln: Ein Name muss mit einem Buchstaben oder einem Unterstrich beginnen. Es kön-
nen beliebig viele Buchstaben, Ziffern oder Unterstriche folgen. Es können alle Buchstaben des Alphabets verwendet werden, mit Ausnahme der
Sonderzeichen. Darunter fallen auch die deutschen Umlaute und das ß.
38
2.1 Syntaxregeln
Es existieren darüber hinaus noch einige reservierte Wörter, die nicht verwendet werden können, weil sie in Python bereits eine Funktion haben oder in Zukunft haben werden. Dabei handelt es sich um folgende Wörter: and, assert, break, class, continue, def, del, elif, else, except, exec, finally, for, from, global, if, import, in, is, lambda, not, or, pass, print, raise, return, try, while, yield
Achten muss man auch auf Namen, die durch das Importieren von Modulen in den Namensraum des Programms gelangen. Hier kann es zu Konflikten kommen. Bei der Vergabe von Namen sollte man darauf achten, dass sie »sprechend« sind. Das bedeutet, dass man z. B. für Variablen Namen verwendet, die einen Hinweis auf den Inhalt bzw. den Wert der Variablen geben. Da man im Programmverlauf über den Namen der Variablen auf ihren Wert zugreift, der Wert sich aber ändern kann, passiert es leicht, dass man nicht mehr weiß, wofür die Variable steht, wenn man keinen sinnvollen Namen verwendet hat. Ein (schlechtes) Beispiel: >>> p = 15 >>> print 'Anzahl der Passagiere:', p Anzahl der Passagiere: 15
Ändert sich die Zahl der Passagiere zwischen Definition der Variablen und Ausgabe durch die print-Anweisung, hat man also noch einige Zeilen Code zwischen Definition und Ausgabe, dürfte es schwierig werden, noch zu wissen, wofür p eigentlich steht. Hier lohnt sich das Mehr an Schreibaufwand für einen aussagekräftigen Namen. Wahrscheinlich spart man sogar Zeit dadurch, weil man nicht suchen muss, wo die Variable ursprünglich definiert wurde: >>> passagiere = 15 >>> print 'Anzahl der Passagiere:', passagiere Anzahl der Passagiere: 15
Will oder muss man einen Namen verwenden, der eigentlich aus mehreren Worten besteht, kann man entweder durch Großbuchstaben oder Unterstriche die Lesbarkeit gewährleisten: >>> datumArbeitsaufnahme = '19/07/2003'
oder >>> datum_arbeitsaufnahme = '19/07/2003'
Man wird beim Lesen von fremden Quelltexten beide Varianten finden, manchmal auch Mischformen. Hier wird jeder seinen eigenen Stil entwickeln. Bei der Arbeit im Team aber gilt auch hier, dass man sich gemeinsame Regeln für die Namensvergabe gibt.
39
2 Skripte schreiben und ausführen
2.2
Skripte ausführen
Neben dem testweisen Ausführen von Skripten in der Entwicklungsumgebung (IDLE oder Eclipse) gibt es auch die Möglichkeit, Skripte über die Eingabeaufforderung (Windows) oder die Konsole bzw. Shell (Linux) auszuführen.
2.2.1
Unter Windows
Voraussetzung für die Ausführung über die Eingabeaufforderung ist, dass der Pfad zum Python-Interpreter zur Umgebungsvariablen PATH (siehe Abschnitt 1.2) hinzugefügt wurde. Ist das der Fall, kann man in der Konsole (Eingabeaufforderung) den Befehl python, gefolgt vom Pfad zum Skript, eingeben, um die Ausführung zu starten (Abbildung 2.1)
Abbildung 2.1:
Python-Skript in der Eingabeaufforderung ausführen
Der Pfad zum Skript kann als relativ eingegeben werden. Befindet man sich im Verzeichnis, in dem das Skript liegt, reicht also die Eingabe des Dateinamens. Eine andere Möglichkeit besteht darin, das Icon des Skripts doppelt anzuklicken. Es öffnet sich die Eingabeaufforderung, das Skript wird ausgeführt und das Fenster schließt sich nach dessen Beendigung sofort wieder. Will man das verhindern, kann man den Befehl raw_input()
ans Ende des Skripts schreiben. Mit diesem Befehl wartet das Skript auf eine Benutzereingabe und das Fenster bleibt geöffnet, bis die (Enter)-Taste gedrückt wird.
40
2.2 Skripte ausführen
Eine dritte Möglichkeit besteht darin, im START-Menü den Punkt AUSFÜHREN... zu wählen und im sich öffnenden Fenster das Kommando python einzugeben, gefolgt vom (absoluten) Pfad des auszuführenden Skripts. Die folgende Abbildung zeigt ein Beispiel:
Abbildung 2.2: Ein Skript über den Ausführen-Dialog ausführen
2.2.2
Unter Linux
Unter Linux ist der Interpreter ohne weitere Maßnahmen bekannt. So kann man in der Shell mit dem Kommando ~$ python pfad/zu/meinem/Skript.py
ein Skript ausführen. Ergebnisse des Skripts werden in der Shell ausgegeben. Man kann aber auch eine Skriptdatei so ausführbar machen, dass man auf das Kommando python verzichten kann. Dazu notiert man in der ersten Zeile seines Skripts eine sogenannte Magic Line, in der man den Pfad zum Python-Interpreter angibt oder den Pfad zu einer Map, in der der Python-Interpreter verzeichnet ist. Beide Möglichkeiten sehen folgendermaßen aus: #!/usr/bin/python #!usr/bin/env python
Anschließend müssen noch die Rechte für die Skriptdatei so verändert werden, dass sie ausführbar wird. Jetzt kann die Skriptdatei in der Konsole über die Angabe ihres relativen Pfads ausgeführt werden. Startet man das Skript in einer grafischen Oberfläche, hat man den gleichen Effekt wie unter Windows. Das Programm erscheint kurz, um sich gleich darauf zu verabschieden. Eine eventuelle Ausgabe kann nicht gelesen werden. Auch hier hilft der Befehl raw_input() am Ende des Skripts, um die Ergebnisse stehen zu lassen.
41
3
Die Struktur von Python
3.1
Daten sind Objekte
Jedes Programm geht mit Daten um. Es erzeugt Daten und verändert diese. Wenn man beispielsweise einen Online-Shop entwickelt, wird man für jedes Produkt im Shop eine Beschreibung und einen Preis haben. Die Beschreibung wird in der Regel ein Text sein, der als Zeichenkette – auch String genannt – vorliegt. Der Preis wird als Zahl vorhanden sein und, da Preise in Euro und Cent angegeben werden, als Zahl mit zwei Stellen hinter dem Komma, als sogenannte Fließkommazahl oder float. Damit sind auch schon die beiden am häufigsten verwendeten Datentypen benannt: Zeichenketten und Zahlen. In Python sind Daten Objekte, mit all den Eigenschaften und Verhalten, die Objekten innewohnen. Das bedeutet beispielsweise, dass Zeichenketten Methoden besitzen, die man auf sie anwenden kann. Ein Beispiel soll das verdeutlichen. Gibt man in der IDLE Folgendes ein: >>> 'es regnet'.capitalize()
erhält man als Ergebnis: 'Es regnet' Die Methode capitalize() liefert eine veränderte Zeichenkette, in der das erste Zeichen als Großbuchstabe und alle weiteren als Kleinbuchstaben ausgegeben werden. Entscheidend aber ist hier der Aufruf als Methode des String-Objekts. In anderen Sprachen würde man eher eine solche Form finden: capitalize('es regnet')
Hier wird einer Funktion mit dem Namen capitalize() die Zeichenkette 'es regnet' übergeben. Die Funktion liefert dann das Ergebnis zurück. Dies ist ein anderes Konzept, als Python es verfolgt. Ein anderes Indiz dafür, dass in Python Daten Objekte sind, kann man bei der Addition von Zahlen feststellen: >>> a = 3 >>> b = 5 >>> a + b 8
3 Die Struktur von Python
Hier wird den Variablen a und b jeweils eine Ganzzahl zugewiesen. Anschließend wird eine Addition der beiden Variablen durchgeführt und das Ergebnis ausgegeben. Hinter der Addition mit dem +-Operator steht die Methode __add__() des Zahlenobjekts. Man kann die gleiche Operation auch folgendermaßen ausführen: >>> a.__add__(b) 8
Man erhält ebenfalls das Ergebnis der Addition. Die Methode __add__() regelt, was bei der Verwendung des Operators + ausgeführt wird. Dieses Konzept der Objektorientierung wird Polymorphismus genannt. Wenn man selbst Klassen für Objekte schreibt, kann man sich dieses Konzept zunutze machen. Welche Methoden ein Objekt besitzt, kann man mit der Funktion dir() ermitteln. Sie gehört zu den sogenannten eingebauten Funktionen (builtin-functions). Gibt man z.B. >>> dir('abc')
In der Ausgabeliste findet sich zum Beispiel die oben verwendete Methode capitalize() neben einer ganzen Reihe weiterer, mit denen Zeichenketten be- und verarbeitet werden können.
3.2
Datentypen
Ein Datentyp zeichnet sich dadurch aus, dass er einen bestimmten Wertebereich wie z. B. ganze Zahlen umfasst. Auf einen solchen Wertebereich lassen sich spezifische Operationen ausführen, bei Zahlen etwa die Addition oder die Subtraktion. Jeder Wertebereich besitzt eigene Operationen. So kann z. B. auf den Wertebereich Zeichenkette eine Subtraktion nicht ausgeführt werden, aber man kann eine Zeichenkette z. B. anhand eines Trennzeichens aufteilen (split()), was wiederum mit Zahlen nicht möglich ist. Die folgenden Abschnitte beschreiben die grundlegenden Datentypen, die Python besitzt, und erläutern die wichtigsten Operationen.
44
3.2 Datentypen
3.2.1
Zahlen
Zahlen sind Datentypen, mit denen mathematische Operationen ausgeführt werden. Mit ihnen wird also gerechnet: >>> 12 + 13 25
Zahlen werden direkt notiert, im Gegensatz zu Zeichenketten, die immer in Anführungszeichen stehen. Die gleiche Operation wie oben mit Zeichenketten kommt zu einem völlig anderen Ergebnis: >>> '12' + '13' '1213'
Dieses kleine Beispiel macht sehr deutlich, dass Zahlen und Zeichenketten sich unterschiedlich verhalten, auch wenn die Ziffern zufällig gleich sind.
Ganzzahlen Wie andere Programmiersprachen auch unterscheidet Python in Ganzzahlen, sogenannte integers (int), und Fließkommazahlen, sogenannte floats. Einfache Ganzzahlen können maximal den Wert 2147483647 annehmen. Alle Zahlen darüber werden als sogenannte long integers (long) behandelt. Diese stellen einen eigenen Datentyp dar, der durch ein nachgestelltes L kenntlich gemacht wird. Gibt man bei der Definition einer Zahl dieses L mit an, erzeugt man selbst einen long integer: >>> 5L 5L
Entsteht durch eine mathematische Operation eine Zahl, die größer ist als der für integers mögliche Maximalwert, wird diese automatisch zu einem long integer: >>> 10**15 1000000000000000L
Neben den Zahlen des Dezimalsystems kann Python auch mit Oktalzahlen und Hexadezimalzahlen umgehen. Oktalzahlen werden mit den Ziffern 0 bis 7 gebildet. Zur Kenntlichmachung wird in Python dem Zahlenausdruck eine 0 vorangestellt. Die Zahl 7 in oktaler Schreibweise
ist demnach >>> 07
Die Zahl 8 wird dann als >>> 010
notiert.
45
3 Die Struktur von Python
Hexadezimale Zahlen werden mit den Ziffern 0–9 sowie den Buchstaben a–f gebildet. Das System besteht also aus 16 Zeichen, mit denen Zahlen gebildet werden können. Benutzt wird dieses System z. B. für Farbangaben in Computerprogrammen. In Python wird eine Hexadezimalzahl mit einem vorangestellten 0x kenntlich gemacht. Die Zahl 15 wird dann als >>> 0xf
notiert, die Zahl 100 als >>> 0x64
Nach Rechenoperationen mit Oktal- oder Hexadezimalzahlen gibt Python Ganzzahlen (int oder long) aus. Folglich ist es auch möglich, Ganz-, Oktal- und Hexadezimalzahlen in Rechenoperationen zu mischen: >>> 0x64 * 010 + 4 804
Im Hintergrund werden Oktal- und Hexadezimalzahlen in Ganzzahlen gewandelt und mit diesen wird die Berechnung durchgeführt.
Fließkommazahlen Kommazahlen sind in Python – wie auch in anderen Sprachen – ein eigener Datentyp (float). Wie schon zu Beginn des Kapitels erwähnt, sind Berechnungen von Preisangaben meistens nur mit Fließkommazahlen möglich. Aber auch Längen- und Gewichtsangaben benötigen Kommazahlen. Man notiert eine Fließkommazahl mit einem Punkt als Dezimaltrennzeichen: >>> 5.2
Es ist möglich, die Zahl vor oder nach dem Komma wegzulassen. Die fehlende Zahl wird dann als 0 gewertet: >>> 5. 5.0
oder >>>.2 0.2
Darüber hinaus existiert eine Expotentialschreibweise, mit der Zahlen mit sehr vielen Stellen vor oder hinter dem Komma notiert werden können: >>> 2.5e3
46
3.2 Datentypen
Dieser Ausdruck entspricht dem Zahlwert 2500 oder dem mathematischen Term 2,5 x 103.
Es ist auch möglich, ein großes E zu verwenden. Gibt man dem Exponenten einen negativen Wert, so erhält man eine Zahl, die in jedem Fall kleiner als 1 ist: >>>2.5e-3
Dieser Ausdruck entspricht dem Zahlwert 0.0025. Python wertet einen Ausdruck in Exponentialschreibweise immer als Fließkommazahl aus, auch dann, wenn die Mantisse eine Ganzzahl ist. Gibt man folgenden Ausdruck ein und drückt den Zeilenschalter, erhält man eine Fließkommazahl als Ergebnis: >>> 2e2 200.0
Fließkommazahlen sind nur begrenzt genau. Gibt man eine Zahl ein mit mehr als 16 Nachkommastellen, dann werden alle folgenden Stellen abgetrennt. Man kann die begrenzte Genauigkeit ebenfalls erkennen, wenn man Folgendes eingibt: >>> 2.4 2.3999999999999999
In der internen Darstellung wird der Wert für die Zahl 2,4 nur näherungsweise repräsentiert. Fehler können auftreten, wenn man mit Fließkommazahlen Additionen und Subtraktionen durchführt. Für exakte Berechnungen mit Dezimalzahlen steht das Modul decimal zur Verfügung.
Komplexe Zahlen Außer in der mathematischen Wissenschaft wird man sie nicht benötigen: die komplexen Zahlen (complex). Sie sind deshalb auch nur der Vollständigkeit halber beschrieben. Eine komplexe Zahl setzt sich aus einer realen und einer imaginären Zahl zusammen. Die Notation in Python ist: >>> 2 + 5.5j (2+5.5j)
Der reale Teil wird durch eine Ganz- oder Fließkommazahl notiert, der imaginäre Teil erhält ein kleines oder großes J angehängt. In der Auswertung wird dem Ausdruck eine Klammer hinzugefügt.
Mathematische Operatoren Natürlich kann man mit Zahlen in Python auch rechnen. Dabei gelten die Regeln, die man aus dem Mathematikunterricht kennt. Die folgende Tabelle gibt einen Überblick über die Operatoren, die man verwenden kann.
47
3 Die Struktur von Python
Operator
Funktion
+
Addition
-
Subtraktion
*
Multiplikation
/
Division
**
Potenz
%
Modulo (Rest einer ganzzahligen Division)
Tabelle 3.1: Mathematische Operatoren Einige Besonderheiten muss man allerdings beachten: Eine Division von Ganzzahlen ergibt eine Ganzzahl. Ist bei der Division ein Operand eine Fließkommazahl, wird auch das Ergebnis
eine Fließkommazahl. Eine Division durch null löst einen Fehler aus (ZeroDivisionError). Wenn in einem Ausdruck unterschiedliche Datentypen erscheinen, ermittelt Python
für das Ergebnis einen geeigneten Datentyp. Wenn ein mathematischer Ausdruck mehrere verschiedene Operatoren enthält, wird eine feste Reihenfolge bei der Ausführung der Operatoren eingehalten. Diese ist: 1. Potenz (**) 2. Vorzeichen (+,-) 3. Multiplikation, Division, Modulo (*,/,%) 4. Addition, Subtraktion (+,_) Operatoren, die auf der gleichen Ebene liegen, werden von links nach rechts ausgewertet. Durch Klammern lässt sich die Auswertungsreihenfolge eines Ausdrucks beeinflussen. Neben diesen Operatoren zur Durchführung von Rechenoperationen stehen noch zwei Operatoren zur Verfügung, mit denen Zahlen näher bestimmt werden. Zwei davon entsprechen den mathematischen Vorzeichen Plus (+) und Minus (-). Der PlusOperator lässt die Zahl unverändert, der Minus-Operator kehrt das Vorzeichen um, was einer Multiplikation der Zahl mit –1 entspricht: >>> >>> -1 >>> >>> 1
48
a = 1 -a b = -1 -b
3.2 Datentypen
3.2.2
Sequenzen
Sequenzen sind Objekte, in denen andere Objekte zusammengefasst werden können. Mit ihnen hat man also die Möglichkeit, mehr als einen Wert unter einem Namen (in einer Variablen) zu speichern. Jedes Element in einer Sequenz ist durch seinen Platz eindeutig bestimmbar. Mit 0 beginnend wird jeder Platz mit einer Indexzahl versehen, über die man auf das einzelne Element zugreifen kann. Das erste Element in einer Sequenz hat somit den Index 0, das zweite den Index 1 usw. In Python ist es auch möglich, den Zugriff auf die Elemente quasi »von hinten« mit negativen Indexzahlen durchzuführen. Das letzte Element einer Sequenz hat dabei den Index –1, das vorletzte den Index –2 usw.
Abbildung 3.1:
Indexierungen einer Sequenz
In Python gibt es zwei Sequenztypen, die sich darin unterscheiden, ob man sie nach ihrer Erstellung verändern kann, also einzelne Elemente herauslöschen, hinzufügen, gegen andere ersetzen oder die Reihenfolge der Elemente verändern kann. Der veränderbare Sequenztyp, der zur Verfügung steht, ist die Liste (list). Ihm gegenüber steht der Tupel (tupel) als nicht veränderbarer Sequenztyp. Ebenfalls als Sequenz, die nicht änderbar ist, wird die Zeichenkette angesehen.
Listen Listen werden durch eckige Klammern gekennzeichnet, die einzelnen Elemente werden durch Kommata getrennt. Es können unterschiedliche Datentypen in einer Liste verwaltet werden. In der Praxis hat man es aber meistens mit gleichen Datentypen zu tun. >>> ziffern = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
Dieser Ausdruck bildet eine Liste mit allen Ziffern und weist sie der gleichnamigen Variablen zu. Da alle Datentypen in Listen abgelegt werden können, kann man auch Listen in Listen speichern. Dadurch ist es möglich, komplexe Datenstrukturen aufzubauen: >>> farben = [['rot', 'gelb', 'blau'], ['red', 'yellow', 'blue'], ['rouge', 'jaune', 'bleu']]
49
3 Die Struktur von Python
Auch die inneren Listen könnten Listen beinhalten usw., so dass die Datenstruktur in ihrer Komplexität deutlich zunimmt. Was möglich ist, ist natürlich nicht immer ratsam. Letztlich ist es vor allem notwendig, dass man die Datenstrukturen, die man aufbaut, langfristig durchschaut und beherrscht. Allzu komplex geschachtelte Listenkonstruktionen sind in der Regel fehlerträchtiger als einfache und klare Datenstrukturen. Ein leere Liste, die erst im weiteren Programmverlauf mit Daten befüllt werden soll, bildet man auf folgende Weise: >>> behaelter = []
Es ist nicht unbedingt notwendig, direkt Werte in eine Liste zu schreiben. Genauso können auch Variablen in eine Liste gepackt werden: >>> d = ['rot', 'gelb', 'blau'], >>> e = ['red', 'yellow', 'blue'], >>> f = ['rouge', 'jaune', 'bleu'] >>> farben = [d, e, f] >>> farben [['rot', 'gelb', 'blau'], ['red', 'yellow', 'blue'], ['rouge', 'jaune', 'bleu']]
Tupel Was für die Bildung von Listen gilt, gilt auch für Tupel, mit dem Unterschied, dass Letztere durch runde Klammern eingefasst werden: >>> ziffern = (1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
Eine Besonderheit ist bei Tupeln mit nur einem Wert zu beachten. Um ein solches Tupel von einem geklammerten Ausdruck zu unterscheiden, fügt man ein Komma nach dem Wert ein: >>> einer = ('eins', )
Es ist beim Erzeugen eines Tupels möglich, die Klammern wegzulassen: >>> ohne = 1, 2, 3 >>> ohne (1, 2, 3)
Zeichenketten Eine Zeichenkette (str) wird durch einfache oder doppelte Anführungszeichen eingefasst. Sie kann Buchstaben, Ziffern, Sonder-, Satz- und Leerzeichen enthalten. Ausnahmen bilden der Backslash (\), das Newline-Zeichen sowie das Anführungszeichen, mit dem die Zeichenkette eingeleitet wurde: >>> satz1 = "Heute geht es um die Wurst" >>> satz2 = 'Er sagte: "Was ist das?"'
50
3.2 Datentypen
Beide Zuweisungen sind gültige Zeichenketten. Im zweiten Fall ist es möglich, in der Zeichenkette doppelte Anführungszeichen zu verwenden, weil sie mit einfachen gebildet wurde. Neben dieser Form der Zeichenkette, die auch kurze Zeichenkette genannt wird und nur in einer Programmzeile definiert werden kann, gibt es auch die Möglichkeit, mehrzeilige Zeichenketten zu bilden, die sich über mehrere Programmzeilen erstrecken. Solche Zeichenketten werden mit drei aufeinanderfolgenden Anführungszeichen (doppelte oder einfache) gebildet: >>> absatz = """Gestern Abend hat er noch lange gearbeitet, weil das Projekt, mit dem er befasst ist, in die Endphase geht. Jetzt heißt es wieder Sonderschichten einlegen."""
Intern behandelt Python lange und kurze Zeichenketten gleich. Wie bereits erwähnt, können einige Zeichen nicht in Zeichenketten verwendet werden. Benötigt man sie dennoch, können sie mit einer sogenannten Escape-Sequenz eingefügt werden. Eine Escape-Sequenz wird mit dem Backslash (\) eingeleitet. Tabelle 3.2 zeigt einige der gebräuchlichsten Escape-Sequenzen. Escape-Sequenz
Ergebnis
\\
Backslash in einer Zeichenkette
\'
Einfaches Anführungszeichen in einer Zeichenkette
\"
Doppeltes Anführungszeichen in einer Zeichenkette
\t
Horizontaler Tabulator
\n
Zeilenumbruch (line feed)
\r
Wagenrücklauf (carriage return)
\ooo
Zeichen aus der ASCII-Tabelle, dessen Nummer als dreistellige Oktalzahl angegeben wird
\xhh
Zeichen aus dem erweiterten ASCII-Satz, dessen Nummer als zweistellige Hexadezimalzahl angegeben wird
\N{Name}
Zeichen mit dem angegebenen universal character name aus der Unicode-Datenbank. (nur Unicode-Strings) Beispiel: u'\N{WHITE SMILING FACE}'
\uhhhh
Zeichen, das über seine Unicode-Nummer in vierstelliger hexadezimaler Schreibweise angegeben wird. (nur Unicode)
Tabelle 3.2: Die gebräuchlichsten Escape-Sequenzen Gibt man deutsche Umlaute oder das »ß« direkt in eine Zeichenkette ein, wandelt Python diese selbstständig in eine Escape-Sequenz um: >>> 'über' '\xfcber'
51
3 Die Struktur von Python
Unicode-Strings Der Datentyp str kann nur Zeichen aus dem ASCII-Satz enthalten. Dieser umfasst nur 128 Zeichen. Die deutschen Umlaute sind darin z. B. nicht enthalten. Mit Unicode steht ein Zeichensatz zur Verfügung, der alle lebendigen Sprachen unterstützt. Er enthält ca. 90000 Zeichen. Jedes einzelne Zeichen benötigt darin allerdings zwei Byte (im ASCII-Satz nur 1 Byte). Aus diesem Grund existiert für Unicode-Zeichenketten ein eigener Datentyp mit Namen unicode. Erzeugt wird eine solche Zeichenkette, indem man ihr ein kleines oder großes U voranstellt: >>> uni = u'Unicode-Zeichenkette' >>> uni u'Unicode-Zeichenkette'
Das Einfügen spezieller Zeichen funktioniert auch hier über Escape-Sequenzen. Entweder man gibt das Zeichen über seinen vierstelligen Hexadezimalcode ein, dann muss die Sequenz mit »\u« beginnen. Oder man verwendet den Unicode-Namen des Zeichens und notiert eine Sequenz nach dem Muster »\N{Unicode-Name}«. Das Euro-Zeichen z. B. erhält man folgendermaßen: >>> print u'Preis: 15,00 \u20ac' Preis: 15,00 _
Über den Unicode-Namen ist die Notation dann: >>> print u'Preis: 15,00 \N{EURO SIGN}' Preis: 15,00 _
Tipp Den Code und die Namen aller Unicode-Zeichen erhält man, unterteilt in sprachspezifische Charts, als PDF-Dateien unter der Webadresse http://www.unicode.org/charts/. Es ist auch möglich, die Daten über eine Datenbank zu beziehen, siehe dazu: http://www.unicode. org/ucd/.
Raw-Strings In einem Raw-String werden keine Escape-Sequenzen verarbeitet. Darin notierte Sequenzen werden als Zeichen aufgefasst, die auszugeben sind. Ein Vergleich macht diese deutlich. Eine einfache Zeichenkette:
52
3.2 Datentypen
>>> print 'Hallo\nWelt' Hallo Welt
Und als Raw-String: >>> print r'Hallo\nWelt' Hallo\nWelt
Wie man sieht, wird der Raw-String mit einem r als solcher kenntlich gemacht (möglich ist auch ein großes R). In der Ausgabe wird die Escape-Sequenz nicht verarbeitet, sondern ihre Zeichen werden ausgedruckt. Hier kann man also Backslashes angeben, die auch als solche ausgegeben werden. Das ist sehr nützlich für Windows-Pfadangaben, die den Backslash als Pfadtrennzeichen benutzen. Ein Pfad als einfache Zeichenkette: >>> 'C:\\Dokumente und Einstellungen\\Default User\\Eigene Dateien\\Eigene Bilder'
Und als Raw-String: >>> 'C:\Dokumente und Einstellungen\Default User\Eigene Dateien\Eigene Bilder'
Auch bei der Arbeit mit regulären Ausdrücken sind Raw-Strings sehr hilfreich, da man mit ihnen leichter lesbare Muster anlegen kann. Raw-Strings sind übrigens kein eigener Datentyp, sondern ebenfalls vom Typ str.
3.2.3
Arbeiten mit Sequenzen
Auf Werte zugreifen Bei der Arbeit mit Sequenzen stehen zwei Aktionen im Vordergrund: das Auslesen der enthaltenen Daten und – bei Listen – das Hinzufügen von Daten. Der Zugriff auf die Daten erfolgt wie bereits erwähnt über den Index, den der einzelne Wert besitzt: >>> ziffern = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] >>> ziffern[5] 6
Wenn die Variable ziffern eine Liste mit allen Ziffern enthält, greift man auf einen einzelnen Wert zu, indem man seinen Index in eckige Klammern nach dem Variablennamen notiert. Genauso funktioniert es, wenn man mit einem negativen Index quasi von hinten zu zählen beginnt: >>> ziffern[-5] 6
53
3 Die Struktur von Python
Unabhängig von der Anzahl der Elemente in der Sequenz erhält man mit dem Index 0 immer den ersten Wert und mit dem Index –1 immer den letzten Wert in der Sequenz. Mit der Standardfunktion len() ermittelt man die Anzahl der Elemente in einer Sequenz: >>> len(ziffern) 10
Diese Art des Zugriffs auf Werte und die Ermittlung der Länge der Sequenz gilt für Listen, Tupel und Zeichenketten gleichermaßen: >>> text = 'es passierte im Morgengrauen' >>> text[10] 't' >>> len(text) 28
Ebenfalls in allen Sequenztypen funktioniert das Extrahieren von Teilsequenzen, das sogenannte Slicing. Dies geht ebenfalls sehr einfach mit der Klammernnotation: >>> farben = ('rot', 'gelb', 'gruen', 'blau', 'orange', 'violett') >>> farben[2:4] ('gruen', 'blau')
Im Prinzip wird hier über die Indexzahlen ein »von ... bis« angegeben. Genauer gesagt ist es »von ... bis ausschließlich«, da der zweite Index das erste Element in der Sequenz bezeichnet, das nicht mehr in die Teilsequenz übernommen wird. Auch hier lässt sich mit negativen Indizes operieren: >>> farben[2:-2] ('gruen', 'blau')
Der zweite Index verweist ebenfalls auf das erste Element, das nicht mehr in die Teilsequenz übernommen wird. Es scheint zunächst etwas unlogisch, dass ein Index benannt werden muss, dessen Element nicht in die Teilsequenz aufgenommen wird. Betracht man die Indizes aber nicht als Plätze, sondern als Grenzpunkte, wird die Sache klarer: Der Index 2 verweist dann auf den Bereich zwischen dem zweiten und dem dritten Elementen, der Index 4 auf den Bereich zwischen dem vierten und dem fünften Element. Alle Elemente, die zwischen diesen Grenzen liegen, werden in die Teilsequenz übernommen. (Als Gedächtnisstütze kann man die Kommata als Grenzpunkte betrachten.) Lässt man beim Slicing eine der beiden Indexzahlen weg, erhält man eine Teilsequenz, die entweder mit dem ersten Element der Sequenz beginnt oder mit dem letzten Element endet: >>> farben[:3] ('rot', 'gelb', 'gruen')
Und natürlich können hier auch wieder negative Indexzahlen verwendet werden. Da dies alles sehr praktisch ist und effizient funktioniert, könnte man auf die Idee kommen, dass ein Ausdruck wie >>> farben[5:3]
eine Teilsequenz liefert, bei der die Reihenfolge der Elemente umgedreht ist, also ('orange', 'blau')
Leider liefert der Ausdruck aber nur eine leere Sequenz. Und dies nicht nur, wenn die Ausgangssequenz ein unveränderbares Tupel ist, sondern auch bei einer Liste. Ohne größere Auswirkungen bleibt der Versuch, beim Slicing auf einen Index zuzugreifen, der in der Sequenz nicht vorhanden ist: >>> farben[3:9] ('blau', 'orange', 'violett')
Es wird einfach das zurückgeliefert, was machbar ist. Das ist beim Zugriff auf ein einzelnes Element anders. Ist der Index nicht vorhanden, wird dies mit einem Fehler quittiert: >>> farben[9] Traceback (most recent call last): File "", line 1, in <module> farben[9] IndexError: tuple index out of range
Neue Werte einfügen Neue Werte kann man nur in Listen einfügen. Tupel und Zeichenketten sind wie bereits erwähnt nicht änderbar. Will man zu einer Liste einen neuen Wert hinzufügen, stellt sich sofort die Frage, wo dieser neue Wert seinen Platz findet. Am einfachsten wird dies mit der Funktion append() beantwortet. Sie fügt den neuen Wert am Ende der Liste ein: >>> fahrzeuge = ['Auto', 'Fahrrad', 'Motorrad'] >>> fahrzeuge.append('Zug') >>> fahrzeuge ['Auto', 'Fahrrad', 'Motorrad', 'Zug']
55
3 Die Struktur von Python
Ebenfalls an das Ende der Liste fügt die Funktion extend() neue Werte ein. Der Unterschied zu append() besteht darin, dass extend() als Parameter eine Sequenz erwartet: >>> fahrzeuge.extend(['Roller', 'Schiff']) >>> fahrzeuge ['Auto', 'Fahrrad', 'Motorrad', 'Zug', 'Roller', 'Schiff']
Gleiches erreicht man auch, indem man die zwei Listen mit dem +-Operator verbindet: >>> fahrzeuge + ['Roller', 'Schiff'] ['Auto', 'Fahrrad', 'Motorrad', 'Zug', 'Roller', 'Schiff']
Will man einen Einzelwert an einer bestimmten Position einfügen, kann man die Methode insert() verwenden. Sie erhält neben dem Wert auch den Index, an dem der neue Wert eingefügt werden soll, als Parameter: >>> fahrzeuge.insert(4, 'Bus') >>> fahrzeuge ['Auto', 'Fahrrad', 'Motorrad', 'Zug', 'Bus', 'Roller', 'Schiff']
Die Elemente ab diesem Index werden um eine Position nach hinten verschoben. Anders verhält es sich, wenn man einem Index direkt einen neuen Wert zuweist: >>> fahrzeuge[4] = 'Boot' >>> fahrzeuge ['Auto', 'Fahrrad', 'Motorrad', 'Zug', 'Boot', 'Roller', 'Schiff']
Der vorherige Wert ist nicht mehr in der Liste vorhanden. Er wurde vom neuen Wert überschrieben. Genauso lassen sich auch Teilsequenzen durch neue Sequenzen ersetzen: >>> fahrzeuge[2:4] = ('Tram', 'Taxi') >>> fahrzeuge ['Auto', 'Fahrrad', 'Tram', 'Taxi', 'Boot', 'Roller', 'Schiff']
Es spielt dabei keine Rolle, ob die hinzugefügte Sequenz ein Tupel ist. Auch eine Zeichenkette würde hinzugefügt, allerdings wäre jeder Buchstabe ein eigenes Element in der Liste.
Werte löschen Um Werte aus einer Liste zu löschen, kann man über den Index vorgehen oder den Wert selbst ansprechen. Im ersten Fall wird wieder die Notation mit eckigen Klammern verwendet: >>> del fahrzeuge[2] >>> fahrzeuge ['Auto', 'Fahrrad', 'Taxi', 'Boot', 'Roller', 'Schiff']
56
3.2 Datentypen
Es wird der Wert an der Indexposition 2 gelöscht. Genauso kann man auch Teilsequenzen löschen: >>> del fahrzeuge[1:3] >>> fahrzeuge ['Auto', 'Boot', 'Roller', 'Schiff']
Will man einen bestimmten Wert aus der Liste löschen, verwendet man die Methode remove() und übergibt ihr diesen: >>> fahrzeuge.remove('Boot') >>> fahrzeuge ['Auto', 'Roller', 'Schiff']
Wenn sich allerdings der angegebene Wert mehrfach in der Liste befinden sollte, wird nur der erste gelöscht, der aufgefunden wird. Befindet sich der Wert gar nicht in der Liste, wird ein Fehler ausgelöst. Mit der Methode pop() wird auch an einer bestimmten Position der Wert gelöscht. Diese Methode gibt aber den gelöschten Wert zurück: >>> fahrzeuge.pop(2) 'Schiff' >>> fahrzeuge ['Auto', 'Roller']
Weitere Listenfunktionen Wenn man ermitteln muss, wie häufig ein bestimmter Wert in einer Liste vorhanden ist, kann man dies mit der Methode count() machen: werte = [12, 19, 23, 13, 44, 19, 56, 23, 19] >>> werte.count(19) 3
Diese Methode eignet sich zur Prüfung, bevor man mit remove() einen Wert löschen will. Ist der Wert nicht vorhanden, gibt die Methode count() 0 zurück. Wenn man den Index eines Werts ermitteln will, steht dafür die Methode index() zur Verfügung. Sie findet den ersten angegebenen Wert und gibt dessen Indexzahl zurück: >>> werte.index(19) 1
Hat man vorher mit count() ein mehrfaches Vorkommen des Werts ermittelt, kann man durch Angabe eines Startindex auch die weiteren Positionen des Werts ermitteln: >>> werte.index(19,2) 5 >>> werte.index(19,6) 8
57
3 Die Struktur von Python
Zunächst wurde – ohne zweiten Parameter – die Liste von Beginn an durchsucht. Dann wurde die Suche einen Platz nach dem ersten Treffer und schließlich nochmals einen Platz nach dem zweiten Treffer fortgesetzt. Auf diese Weise wurden die drei Indizes des Werts 19 ermittelt. Mit einem dritten Parameter lässt sich die Suche nach dem Index eines Werts auf eine Teilsequenz beschränken. Der dritte Parameter markiert den Endpunkt der Teilsequenz. Ist der Wert, dessen Index gesucht wird, nicht in der Liste vorhanden, wird ein Fehler ausgelöst. Mit zwei weiteren Funktionen kann man die Reihenfolge der Listenelemente beeinflussen. Mit reverse() wird die Reihenfolge umgekehrt: >>> werte.reverse() >>> werte [19, 23, 56, 19, 44, 13, 23, 19, 12]
Vergleicht man das Aussehen der Liste mit jener zu Beginn dieses Abschnitts, sieht man die Wirkung der Methode reverse() deutlich. Die Methode sort() sortiert die Listenelemente aufsteigend: >>> werte.sort() >>> werte [12, 13, 19, 19, 19, 23, 23, 44, 56]
Auch hier sind die Auswirkungen wieder deutlich zu erkennen: Es geht nun schön der Reihe nach. Allerdings kann man die Sortierung der Methode durch einige Parameter beeinflussen. Zunächst ist da der Parameter reverse, der standardmäßig auf False gesetzt ist. Setzt man ihn auf True, wird nunmehr absteigend sortiert: >>> werte.sort(reverse=True) >>> werte [56, 44, 23, 23, 19, 19, 19, 13, 12]
Mit dem Parameter cmp kann man eine Funktion benennen, anhand derer die Sortierung vorgenommen wird. Dies kann eine selbst definierte Sortierfunktion sein. Allerdings muss sie die beiden Parameter a und b haben sowie einen negativen Wert, null oder einen positiven Werte zurückgeben, je nachdem, ob a kleiner, gleich oder größer b ist. Eine Sortierfunktion kann man ebenfalls mit dem Parameter key benennen. Diese muss aber nicht nach dem Schema der zuvor beschriebenen Sortierfunktion aufgebaut sein. Vielmehr benötigt sie nur einen Parameter, dem die Werte der Liste nacheinander übergeben werden. Sortiert wird der Rückgabewert der Funktion.
58
3.2 Datentypen
3.2.4
Assoziative Felder
Das assoziative Feld ist ein Datentyp, in dem Daten als Schlüssel-Werte-Paare abgelegt werden. In Python wird dieser Datentyp Dictionary (dict) genannt. Ein Dictionary wird in geschweifte Klammern eingefasst, Schlüssel und Werte werden durch einen Doppelpunkt zusammengehalten und die Datenpaare werden durch ein Komma getrennt. Ein einfaches Anwendungsbeispiel ist das Speichern einer Adresse, die aus Name, Vorname, Straße, Postleitzahl und Ort besteht: >>> adresse = {'name' : 'Mustermann', 'vorname' : 'Hans', 'strasse' : 'Musterweg 6', 'plz' : '12345', 'ort' : 'Musterstadt'}
Es ist dabei nicht notwendig, jedes Datenpaar in eine neue Zeile zu notieren. Aber in dieser Form ist es sehr übersichtlich und – nebenbei bemerkt – der Satzspiegel des Buchs lässt es nicht zu, das Dictionary in einer Zeile zu notieren. Über den Schlüssel erhält man Zugriff auf den Wert. Wenn also der Name aus dem Dictionary adresse ausgelesen werden soll, geschieht das folgendermaßen: >>> adresse['name'] 'Mustermann'
Ein solches Dictionary erinnert an einen Datensatz aus einer Datenbanktabelle. Tatsächlich kann man mit gleich strukturierten Dictionarys, die in eine Liste abgelegt werden, tabellenartige Datenstrukturen aufbauen. Als Schlüssel kann man nur nicht änderbare Datentypen verwenden. Häufig werden Zeichenketten benutzt, aber auch Zahlen (int und float) sowie Tupel sind möglich. Wenn eine Variable einen der genannten Datentypen enthält, kann auch sie als Schlüssel verwendet werden. Als Wert lässt sich jeder Datentyp in einem Dictionary abspeichern, letztlich jede Art von Objekt, auch solche, die auf selbst definierten Klassen basieren.
3.2.5
Arbeiten mit Dictionarys
Auf Werte zugreifen Der Zugriff über den Schlüssel beinhaltet das Problem, dass der abgefragte Schlüssel im Dictionary nicht vorhanden ist. In einem solchen Fall wird eine Fehlermeldung ausgelöst:
59
3 Die Struktur von Python
>>> adresse['telefon'] Traceback (most recent call last): File "", line 1, in <module> adresse['telefon'] KeyError: 'telefon'
Diesen Fehler vermeidet man, wenn man mit der Methode get() einen Schlüssel abfragt: >>> adresse.get('telefon') None
Ist der Schlüssel im Dictionary nicht vorhanden, gibt die Methode None zurück, andernfalls den zum Schlüssel gehörenden Wert. Mit einem zweiten Parameter kann ein Standardwert definiert werden, der zurückgegeben wird, wenn der abgefragte Schlüssel nicht vorhanden ist: >>> adresse.get('telefon','Nicht eingetragen') 'Nicht eingetragen'
Ähnlich wie get() funktioniert die Methode setdefault(), mit dem Unterschied, dass ein neues Schlüssel-Werte-Paar eingefügt wird, wenn der Schlüssel noch nicht im Dictionary vorhanden ist: >>> adresse.setdefault('telefon','123 456 78') '123 456 78' >>> adresse {'name': 'Mustermann', 'ort': 'Musterstadt', 'plz': '09876', 'telefon': '123 456 78', 'vorname': 'Hans', 'strasse': 'Musterweg 6'}
Eine andere Möglichkeit, den oben gezeigten Fehler zu vermeiden, besteht darin, mit der Methode has_key() zunächst zu prüfen, ob ein bestimmter Schlüssel im Dictionary vorhanden ist: >>> if adresse.has_key('telefon'): print adresse['telefon']
Wenn im Dictionary der abgefragte Schlüssel existiert, gibt die Methode True zurück, andernfalls False. Die obige Abfrage des Schlüssels 'telefon' wird also nur dann ausgeführt, wenn der Schlüssel tatsächlich existiert.
Werte hinzufügen, Werte entfernen Ein neues Schlüssel-Werte-Paar in ein Dictionary einzufügen, ist denkbar einfach. Es wird lediglich der Schlüssel in eckige Klammern zum Namen des Dictionary notiert und eine Wertzuweisung vorgenommen: >>> adresse['telefon'] = '123 456 78' >>> adresse {'name': 'Mustermann', 'ort': 'Musterstadt', 'plz': '12345', 'telefon': '123 456 78', 'vorname': 'Hans', 'strasse': 'Musterweg 6'}
60
3.2 Datentypen
Das neue Schlüssel-Werte-Paar wird an einem beliebigen Platz innerhalb des Dictionary eingefügt. Da der Zugriff über den Schlüssel erfolgt, hat die Position eines Paares im Dictionary keine Bedeutung. Ist der Schlüssel im Dictionary bereits vorhanden, wird dessen Wert überschrieben: >>> adresse['plz'] = '09876' >>> adresse {'name': 'Mustermann', 'ort': 'Musterstadt', 'plz': '09876', 'telefon': '123 456 78', 'vorname': 'Hans', 'strasse': 'Musterweg 6'}
Mit der Anweisung del löscht man ein Schlüssel-Werte-Paar aus dem Dictionary: >>> del adresse['telefon'] >>> adresse {'name': 'Mustermann', 'ort': 'Musterstadt', 'plz': '09876', 'vorname': 'Hans', 'strasse': 'Musterweg 6'}
Will man den gelöschten Wert zurückgeliefert bekommen, muss man die Methode pop() verwenden. Sie löscht das Schlüssel-Werte-Paar mit dem angegebenen Schlüssel und gibt den Wert zurück. Wenn nicht klar ist, ob der Schlüssel vorhanden ist, kann mit einem zweiten Parameter ein Standardwert angegeben werden, der zurückgegeben wird. >>> adresse.pop('vorname') 'Hans' >>> adresse {'strasse': 'Musterweg 6', 'ort': 'Musterstadt', 'name': 'Mustermann', 'plz': '09876'}
Jetzt, nachdem 'vorname' gelöscht ist, würde der gleiche Aufruf der Methode pop() einen Fehler auslösen. Deshalb: >>> adresse.pop('vorname','nicht vorhanden') 'nicht vorhanden'
Schließlich kann man mit der Methode clear() das gesamte Dictionary leeren, also alle Schlüssel-Werte-Paare löschen: >>> adresse.clear() {}
Dictionarys zerlegen und zusammenfügen In manchen Situationen kann es von Vorteil sein, wenn man mit einer Liste statt mit einem Dictionary arbeitet. Es existieren drei Methoden, die ein Dictionary entsprechend zerlegen können. Mit der Methode items() erhält man eine Liste, die Tupel enthält, mit je einem Schlüssel und einem Wert:
An der ersten Position im Tupel befindet sich der Schlüssel an der zweiten der Wert. Das Dictionary selbst bleibt von dieser Operation unberührt. Mit der Methode keys() erhält man eine Liste aller Schlüssel und mit der Methode values() eine Liste aller Werte: >>> adresse.keys() ['strasse', 'ort', 'vorname', 'name', 'plz'] >>> adresse.values() ['Musterweg 6', 'Musterstadt', 'Hans', 'Mustermann', '09876']
In den beiden entstehenden Listen befinden sich die Schlüssel und die zugehörigen Werte immer an denselben Positionen, sodass der Bezug zwischen Schlüssel und Wert erhalten bleibt. Die Methoden iteritems(), iterkeys() und itervalues() liefern die gleichen Ergebnisse als Iteratoren. Ein Iterator ist ein Objekt, das keinen Index und keine Schlüssel besitzt. Der Zugriff auf die Werte erfolgt über einen Zeiger – Cursor genannt –, der mit der Methode next() Wert für Wert vorwärts bewegt wird: >>> s = adresse.iteritems() >>> s
Man kann den Inhalt des Iterators nicht direkt ausgeben. Der Versuch liefert nur die Objektadresse. Auch der Zugriff über Indizes funktioniert nicht, sondern wird mit einem Fehler quittiert: >>> s[0] Traceback (most recent call last): File "", line 1, in <module> s[0] TypeError: 'dictionary-itemiterator' object is unsubscriptable
Lediglich mit der Methode next() kann man nacheinander die Inhaltselemente ausgeben: >>> s.next() ('strasse', 'Musterweg 6') >>> s.next() ('ort', 'Musterstadt')
Iteratoren sind dann besonders effizient, wenn klar ist, dass Wertzugriffe wirklich nur der Reihe nach erfolgen.
62
3.2 Datentypen
Mit der Methode update() schließlich ist es möglich, zwei oder mehrere Dictionarys zusammenzufassen. Genauer gesagt, wird ein Dictionary um die Daten des zweiten erweitert: >>> g_daten = {'geburtstag': '19.07.1978', 'geburtsort': 'Frischberg'} >>> adresse.update(g_daten) >>> adresse {'vorname': 'Hans', 'name': 'Mustermann', 'geburtsort': 'Frischberg', 'strasse': 'Musterweg 6', 'ort': 'Musterstadt', 'geburtstag': '19.07.1978', 'plz': '09876'} >>> g_daten {'geburtstag': '19.07.1978', 'geburtsort': 'Frischberg'}
Wenn dabei gleiche Schlüssel in beiden Dictionarys vorhanden sind, werden die Werte des Dictionary übernommen, das der Methode update() übergeben wird. Nach der Operation existiert dieses Dictionary unabhängig weiter.
3.2.6
Mengen
Als Menge wird in Python eine Sammlung von Objekten bezeichnet, in der jedes Objekt genau einmal vorkommt. Sie haben eine gewisse Ähnlichkeit mit Sequenzen, besitzen aber keine Indizes, über die man auf einzelne Objekte zugreifen könnte. Es ist aber möglich, sie mithilfe einer Schleife komplett auszulesen. Man sagt auch, sie sind iterierbar. Es gibt in Python zwei Arten von Mengen, die jeweils einen eigenen Datentyp darstellen: die veränderbare (set) und die nicht veränderbare (frozenset) Menge. Eine veränderbare Menge wird gebildet durch die Funktion set(), die eine Sequenz oder ein Dictionary als Parameter erwartet. Wird ein Dictionary an die Funktion übergeben, werden nur dessen Schlüssel in die Menge übernommen. Ein Beispiel: >>> menge = set('Hallo Welt') >>> menge set(['a', ' ', 'e', 'H', 'l', 'o', 't', 'W'])
Die Ausgabe zeigt, dass das 'l' aus der Zeichenkette nur einmal aufgenommen wurde. Ferner entspricht die Reihenfolge der Buchstaben in der Menge nicht der Reihenfolge in der Zeichenkette. Mengen sind ungeordnet. Die Bildung einer nicht änderbaren Menge erfolgt mit der Funktion frozenset() auf die gleiche Weise: >>> zahlen = frozenset([4,6,7,3,6,1,4,8,9,3,4,5,0,3,4,2,6]) >>> zahlen frozenset([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Hier wurde eine Liste mit Zahlen an die Funktion übergeben, die daraus eine Menge erstellt, die jede vorkommende Zahl genau einmal enthält.
63
3 Die Struktur von Python
3.2.7
Spezielle Typen
In bestimmten Anwendungssituationen benötigt man nicht unbedingt konkrete Werte, sondern lediglich einen Schalter, der zwischen »an« und »aus« unterscheidet. Man spricht hier auch von einem flag. Andere Situationen erfordern einen Namen bzw. eine Variable, die aber (noch) keinen Wert besitzt. Für beide Situationen stellt Python den passenden Datentyp bereit. Die Unterscheidung von zwei Zuständen wird mit dem Datentyp bool vorgenommen. Der wertlose Datentyp trägt die Bezeichnung NoneType. Wie sie gebildet werden, zeigen die beiden folgenden Beispiele. Zunächst der Datentyp bool: >>> a = False >>>a False
Die Ausgabe in die Python-Shell ist keine Zeichenkette, sondern die Repräsentanz des bool-Objekts. Der Datentyp NoneType wird auf diese Weise gebildet: >>> b = None >>> b >>>
Da der NoneType keinen Wert enthält, wird folglich auch keine Ausgabe in die Python-Shell gemacht. Funktionen, die keine Werte zurückgeben, also keine return-Anweisung besitzen, geben automatisch den Wert None zurück.
3.2.8
Typenwandlung
In Python gibt es keine Typendeklaration für Variablen. Eine Variable nimmt einen Typ implizit durch die Wertzuweisung an. Aus diesem Grund ist es möglich, einer Variablen jederzeit einen anderen Datentyp zuzuweisen: >>> x = 'hallo' >>> x 'hallo' >>> x = 444 >>> x 444
Was Programmierern von »strengeren« Sprachen die Haare zu Berge stehen lässt, geht einem in Python locker von der Hand. Zunächst erhält die Variable eine Zeichenkette als Wert und anschließend eine Ganzzahl. Wir sprechen hier natürlich nicht
64
3.2 Datentypen
über Programmierstil, sondern nur von einer Möglichkeit, die in Python vorhanden ist. Guter Stil in der Programmierung sieht sicherlich anders aus als ständiges Wechseln der Datentypen. Aber es kann Situationen geben, in denen man explizit einen Datentyp in einen anderen konvertieren muss. Dies kann zum Beispiel der Fall sein, wenn man Benutzereingaben verarbeiten muss. Soll ein Benutzer über eine Schnittstelle (z. B. eine Webseite) Zahlen eingeben, mit denen anschließend Berechnungen durchgeführt werden, so müssen die Zahlen, die nach der Eingabe zunächst als Zeichenketten vorliegen, in Ganzzahlen oder Fließkommazahlen umgewandelt werden. Ein Beispiel soll das verdeutlichen: zahl1 = raw_input('1. Zahl: ') zahl2 = raw_input('2. Zahl: ') ergebnis = zahl1 + zahl2 print 'Ergebnis: ', ergebnis
Dieses kurze Skript soll die Summe zweier vom Benutzer eingegebenen Zahlen berechnen. Führt man es aus, bekommt man z. B. folgenden Ablauf: 1. Zahl: 3 2. Zahl: 4 Ergebnis: 34
Man erwartet eigentlich, dass bei der Addition der Wert 7 herauskommt. Die Funktion raw_input(), die hier die Schnittstelle zum Benutzer darstellt, liefert allerdings eine Zeichenkette zurück. Der +-Operator wird also in der dritten Zeile auf zwei Zeichenketten angewandt, die damit zu einer verbunden werden. Will man die Berechnung (mathematisch) korrekt durchführen, muss man die Zeichenketten in Zahlen wandeln, ohne dass die Zahlwerte verloren gehen. Für diese Aufgabe steht eine Funktion zur Verfügung: zahl1 = raw_input('1. Zahl: ') zahl2 = raw_input('2. Zahl: ') ergebnis = int(zahl1) + int(zahl2) print 'Ergebnis: ', ergebnis
Nach dieser Änderung sieht der Programmablauf so aus: 1. Zahl: 3 2. Zahl: 4 Ergebnis: 7
Es wurde die Funktion int() verwendet. Diese wandelt die Zeichenketten in Ganzzahlen, mit denen die Berechnung nun ausgeführt werden kann.
65
3 Die Struktur von Python
Es stehen eine Reihe weiterer Funktionen zur Verfügung, mit denen Typumwandlungen vorgenommen werden können. Tabelle 3.3 zeigt einen Überblick der Funktionen zur Typenwandlung: Funktion
Ergebnistyp
Erlaubte Parametertypen
Ergebnis ohne Parameter
int()
Ganzzahl
float, long, str (aus Ziffern bestehend)
0
float()
Fließkommazahl
int, long, str (aus Ziffern bestehend)
0.0
long()
Lange Ganzzahl
int, float, str (aus Ziffern bestehend)
0L
complex()
Komplexe Zahl
int, long, float, str (aus Ziffern bestehend) 0j
str()
Zeichenkette
jeder Typ
''
unicode()
Unicode-Zeichenkette
jeder Typ
u''
list()
Liste
str, unicode, tuple, dict (nur die Schlüssel [] werden in die entstehende Liste aufgenommen)
tuple()
Tupel
str, unicode, list, dict (nur die Schlüssel werden in das entstehende Tupel aufgenommen)
()
dict()
Dictionary
list, tuple (es müssen in den Sequenzen Wertepaare enthalten sein)
{}
bool()
Boolscher Typ
jeder Typ
False
Tabelle 3.3: Funktionen zur Typumwandlung Eine Besonderheit stellen die Funktionen int() und long() dar. Ihnen kann als zweiter Parameter eine Zahlenbasis übergeben werden, anhand derer der Wert für den ersten Parameter gewandelt wird. Verwendet man den ersten Parameter, muss für den ersten eine Zeichenkette übergeben werden. Ein Beispiel: >>> int('1750',8) 1000
Es wird die Zeichenkette mit der Zahl 1750 zur Basis 8, also als Oktalzahl, gewandelt. Das Ergebnis ist die Zahl 1000 als Ganzzahl. Bei der Verwendung einer Basis muss man darauf achten, dass sich die zu wandelnde Zahl auch auf dieser Basis auswerten lässt. Andernfalls erhält man eine Fehlermeldung.
66
3.3 Flache und tiefe Kopien von Listen und Dictionarys
3.3
Flache und tiefe Kopien von Listen und Dictionarys
Während man Variablen mit Einzelwerten, also z. B. Ganzzahlen, durch eine einfache Zuweisung an eine andere Variable kopieren kann, ist das mit Listen und Dictionarys so nicht möglich. Ein einfacher Test zeigt das, zunächst mit einer Ganzzahl: >>> >>> >>> 3 >>> >>> 5 >>> 3
a = 3 b = a b b = 5 b a
Nun mit einem Dictionary: >>> dict1 = {'a':1,'b':5} >>> dict2 = dict1 >>> dict2['a'] = 3 >>> dict2 {'a': 3, 'b': 5} >>> dict1 {'a': 3, 'b': 5}
Wie man sieht, wirkt sich die an dict2 vorgenommene Änderung auch auf dict1 aus. Tatsächlich wird hier mit ein und demselben Objekt gearbeitet, nur unter verschiedenen Namen. Dies ist unbedingt zu beachten, wenn man Dictionarys – und das gilt genauso für Listen – kopieren will. Mit dem Operator = erreicht man eine Zuweisung des gleichen Objekts an einen weiteren Namen. Man kann aber auch Kopien von Dictionarys und Listen erzeugen. Dafür steht das Modul copy zur Verfügung, das folgendermaßen benutzt wird: >>> import copy >>> dict1 = {'a':10,'b':50} >>> dict2 = copy.copy(dict1) >>> dict2['a'] = 12 >>> dict2 {'a': 12, 'b': 50} >>> dict1 {'a': 10, 'b': 50}
Das Modul copy besitzt die gleichnamige Methode, der das zu kopierende Objekt übergeben wird. Ändert man nun an der so erzeugten Kopie einen Wert, wirkt sich das nicht mehr am Original aus. Beide Dictionarys sind voneinander unabhängig.
67
3 Die Struktur von Python
Allerdings gibt es dabei eine Einschränkung: Besitzt das Dictionary (oder die Liste) als Werte wiederum Dictionarys oder Listen, bleiben diese im Original und in der Kopie miteinander verbunden: >>> dict1 = {'a':[1,2],'b':[5,6]} >>> dict2 = copy.copy(dict1) >>> dict2['a'][0] = 11 >>> dict2 {'a': [11, 2], 'b': [5, 6]} >>> dict1 {'a': [11, 2], 'b': [5, 6]}
Geändert wird der erste Eintrag der Liste, die unter dem Schlüssel a im Dictionary abgelegt ist. Und man sieht, dass sich diese Änderung sowohl auf die Kopie als auch auf das Original auswirkt. Aber auch um komplett entkoppelte Kopien zu erzeugen, kann das Modul copy verwendet werden. Es besitzt für diesen Zweck die Methode deepcopy(). Mit ihrer Hilfe ist das Ergebnis aus dem vorigen Beispiel Folgendes: >>> dict1 = {'a':[1,2],'b':[5,6]} >>> dict2 = copy.deepcopy(dict1) >>> dict2['a'][0] = 11 >>> dict2 {'a': [11, 2], 'b': [5, 6]} >>> dict1 {'a': [1, 2], 'b': [5, 6]}
Jetzt sind auch die Listen im Original und in der Kopie voneinander unabhängig und die beiden Dictionarys vollkommen voneinander getrennt. Und auch diese Methode lässt sich auf Listen gleichermaßen anwenden.
3.4
Kontrollfluss
Auch in Python werden Skripte zeilenweise von oben nach unten vom Interpreter gelesen und abgearbeitet. Mit bedingten Anweisungen und Schleifen kann man diesen Fluss manipulieren.
3.4.1
Bedingte Anweisungen
Die bedingte Anweisung prüft zunächst, ob eine definierte Bedingung zutrifft, bevor ein Befehl oder ein Set von Befehlen (Befehlsblock) abgearbeitet wird. Diese einfachste Form der bedingten Anweisung wird in Python nach folgendem Muster aufgebaut: if Bedingung: Anweisung(en)
68
3.4 Kontrollfluss
Die bedingte Anweisung beginnt immer mit dem Schlüsselwort if. Ihm folgt eine Bedingung und den Abschluss bildet ein Doppelpunkt. Eine Zeile darunter wird dann eingerückt der Anweisungsblock notiert (siehe auch Abschnitt 2.1.2). Besteht der Anweisungsblock nur aus einer Anweisung, kann diese auch direkt nach dem Doppelpunkt in der Zeile der if-Bedingung notiert werden. Ein kurzes Beispielskript erläutert die Funktionsweise der bedingten Anweisung: alter = input('Geben Sie ihr Alter ein: ') if alter < 18: print 'Sie sind noch nicht erwachsen!' if alter >= 18: print 'Sie sind erwachsen!'
Wenn man das Skript ausführt, hat man z. B. folgenden Programmfluss: Geben Sie ihr Alter ein: 12 Sie sind noch nicht erwachsen!
Die Funktion input() liefert die Schnittstelle zur Benutzereingabe. Anders als bei raw_input() richtet sich der Datentyp des Rückgabewerts nach dem Ausdruck, den der Nutzer eingibt. Ist dies eine Zahl, ist der Rückgabewert eine Ganzzahl (int). Diese Zahl wird nun in den beiden if-Anweisungen geprüft. In der ersten wird geprüft, ob die Zahl kleiner als 18 ist. Die zweite prüft, ob die Zahl größer oder gleich 18 ist. Die print-Befehle werden jeweils nur dann ausgegeben, wenn die Prüfung positiv ausfällt, oder anders ausgedrückt, wenn der Bedingungsausdruck True ist. Man kann einen Bedingungsausdruck auch direkt in der Python-Shell testen, um zu sehen, wie Python ihn auswertet: >>> 12 < 18 True >>>12 >= 18 False
Nimmt man die erste Eingabe und testet damit die beiden Bedingungsausdrücke, sieht man, dass im ersten Fall der Ausdruck als True und zweiten Fall als False ausgewertet wird. Liegt eine True-Auswertung vor, wird der anschließende Anweisungsblock ausgeführt. Das Beispiel ist ein wenig umständlich, da es auch die Möglichkeit gibt, mit einem else-Block zu bestimmen, welche Anweisungen ausgeführt werden sollen, wenn eine Bedingung nicht zutrifft. Das geschieht nach folgendem Muster: if Bedingung: Anweisung(en) else: Anweisung(en)
69
3 Die Struktur von Python
Das Schlüsselwort else wird in der Zeile unter dem ersten Anweisungsblock notiert. Es muss die gleiche Einrückungstiefe haben wie die if-Zeile, andernfalls reagiert der Interpreter mit einer Fehlermeldung. Auch dem else folgt ein Doppelpunkt und dann in einer neuen Zeile und eingerückt der Anweisungsblock. Dessen Einrückungstiefe sollte der des Blocks unter der if-Bedingung entsprechen. Das vorige Beispiel kann nunmehr etwas vereinfacht werden: alter = input('Geben Sie ihr Alter ein: ') if alter < 18: print 'Sie sind noch nicht erwachsen!' else: print 'Sie sind erwachsen!'
Man kommt hier also mit nur einer Bedingung aus und kann im else-Block definieren, welche Anweisungen ausgeführt werden, wenn die Bedingung nicht erfüllt ist. Der Programmablauf unterscheidet sich nicht vom ersten Beispiel. Eine dritte Möglichkeit, eine bedingte Anweisung zu verwenden, besteht darin, mehrere Alternativen zu prüfen und je nach Ergebnis verschiedene Anweisungen auszuführen. Das Muster dazu sieht folgendermaßen aus: if Bedingung1: Anweisung(en) elif Bedingung2: Anweisung(en) elif Bedingung3: Anweisung(en) else: Anweisung(en)
Hier wird zunächst wie gewohnt eine Bedingung definiert und ein Anweisungsblock darunter notiert. Eingeleitet durch das Schlüsselwort elif (else if) kann eine weitere Bedingung notiert werden. Auch diese muss mit dem Doppelpunkt abgeschlossen werden. Es folgt ein Anweisungsblock, der ausgeführt wird, wenn diese zweite Bedingung zutrifft. Solche alternativen Bedingungen können beliebig häufig wiederholt werden. Zum Abschluss kann optional ein else-Block notiert werden, der dann ausgeführt wird, wenn keine der zuvor definierten Bedingungen zutrifft. Auch hier wieder ein Beispiel zur Verdeutlichung: alter = input('Geben Sie ihr Alter ein: ') if alter < 5: print 'Kleinkind' elif alter < 12: print 'Kind' elif alter < 18: print 'Jugendlicher' else: print 'Sie sind erwachsen!'
70
3.4 Kontrollfluss
Dieser Skript differenziert die Benutzereingabe nach der Altersgruppe. Es werden alle Bedingungen von oben nach unten geprüft, bis eine mit True ausgewertet wird. Die zu dieser Bedingung notierten Anweisungen werden dann ausgeführt. Wird keine der Bedingungen positiv getestet, kommt der else-Block zum Tragen. Mit solchen if-elif-else-Konstruktionen werden in Python Fallunterscheidungen bestimmt. Eine spezielle Anweisung wie etwa switch – case in anderen Sprachen existiert nicht.
3.4.2
Operatoren
Für die Definition von Bedingungen sind Operatoren notwendig, die eine Überprüfung vornehmen. Es gibt Operatoren, die Objekte vergleichen, Operatoren, die die Zugehörigkeit eines Objekts zu einer Sequenz prüfen, und Operatoren, die Bedingungen logisch miteinander verknüpfen.
Vergleichsoperatoren Der einfachste Vergleich, der angestellt werden kann, ist, ob zwei Objekte bzw. zwei Variablen den gleichen Wert haben. Der Operator für diesen Vergleich ist das doppelte Gleichheitszeichen (==): >>> a = 7 >>> a == 7 True
Will man prüfen, ob zwei Variablen nicht gleiche Werte haben, dann gibt es auch dafür einen Operator: >>> b = 6 >>> b != 5 True
Beim Prüfen auf gleiche Werte wird das doppelte Gleichheitszeichen benötigt, weil das einfache Gleichheitszeichen als Zuweisungsoperator fungiert. Wenn man nur überprüfen will, ob ein Objekt einen True-Wert hat, kann man in einer if-Bedingung den Operator weglassen: >>> b = True >>> if b == True: print 'es stimmt!'
Dies geht auch kürzer: >>> b = True >>> if b: print 'es stimmt!'
71
3 Die Struktur von Python
Nicht nur der boolesche Datentyp kann einen True-Wert annehmen, sondern jeder andere Datentyp auch. Und genauso kann jeder Datentyp einen False-Wert annehmen. Tabelle 3.4 listet alle False-Werte der Datentypen auf. Alle übrigen Werte werden als True ausgewertet. Datentyp
False-Wert
Int
0
Long
0L
Float
0.0
Complex
0j
List
[]
Tuple
()
Dictionary
{}
Bool
False
NoneType
None
Tabelle 3.4: Die False-Werte der Datentypen Ein Beispiel: >>> liste = [] >>> if liste: print 'Es sind Einträge vorhanden.' else: print 'Die Liste ist noch leer.' Die Liste ist noch leer
Die Bedingung prüft, ob Einträge in der Liste vorhanden sind. Ist dies der Fall, wird die Liste als True ausgewertet, andernfalls als False. Im Beispiel ist die Liste leer, also wird die Anweisung im else-Block ausgeführt. Um zu prüfen, ob Werte in bestimmten Bereichen liegen, stehen die Operatoren größer (>), kleiner (<), größer gleich (>=) und kleiner gleich (<=) zur Verfügung. Für Zahlen ist die Anwendung dieser Operatoren recht einfach: >>> a = 7 >>> a < 10 True
oder: >>> b = 125 >>> c = 1250 >>> b > c False
72
3.4 Kontrollfluss
Diese Operatoren können aber auch für andere Datentypen verwendet werden: >>> 'a' > 'b' False
Bei Zeichenketten, also Sequenzen, werden die einzelne Elemente verglichen. Wenn die Elemente Buchstaben sind, ist ihre Position in der ASCII-Tabelle ausschlaggebend. Da der Buchstabe a vor dem Buchstabe b in der Tabelle liegt, ist seine »Platznummer« die kleinere und somit wird der Ausdruck als False ausgewertet. >>> 'halt' > 'hals' True
Bei diesem Beispiel wird der Ausdruck als True gewertet, weil die erste Zeichenkette an der vierten Stelle den Buchstaben mit der höheren »Platznummer« hat. Die ersten drei Buchstaben haben identische Plätze in der ASCII-Tabelle. >>> 'galt' > 'hals' False
Mit der Änderung des ersten Buchstabens sieht die Sache schon wieder ganz anders aus: Das g hat gegenüber dem h die kleinere »Platznummer«. Also wird der Ausdruck als False ausgewertet. Sequenzen werden also Element für Element verglichen. Es spielt dabei keine Rolle, ob eine Sequenz mehr Elemente beinhaltet als die andere: >>> 'halt' > 'halteverbot' False
aber: >>> 'kalt' > 'halteverbot' True
Im ersten Fall unterscheiden sich die Sequenzen erst ab der fünften Stelle. Da es diese in der ersten Sequenz nicht gibt, wird sie mit None gewertet und ist damit auf keinen Fall größer als die fünfte Stelle in der zweite Sequenz. Im zweiten Fall ist bereits an der ersten Stelle ein Unterschied festzustellen. Die kürzere Sequenz wird als »größer« gewertet, weil ihr erster Buchstabe die höhere »Platznummer« hat. In Python kann man Vergleichsoperatoren miteinander verketten. Dadurch ist es z. B. möglich zu testen, ob eine Variable innerhalb eines bestimmten Wertebereichs liegt: >>> a = 12 >>> 10 <= a <= 20 True
Mit diesem kurzen und einfachen Ausdruck wird geprüft, ob der Wert der Variablen a zwischen 10 und 20 liegt.
73
3 Die Struktur von Python
In Python existiert noch ein weiterer Vergleichsoperator, der die Identität zweier Objekte prüft. Dies ist der Operator is. Jedes Objekt besitzt in Python eine Id. Dadurch ist es eindeutig identifizierbar. Überprüfen kann man diese Id mit der Funktion id(): >>> id('a') 8996512
Nicht änderbare Objekte besitzen die gleiche Identität. Das gilt auch für Variablen, denen nicht änderbare Objekte zugewiesen werden: >>> a = 'hallo' >>> b = 'hallo' >>> a is b True
Beide Variablen werden als identisch ausgewertet. Anders sieht es aber aus, wenn man änderbare Objekte wie z. B. Listen miteinander vergleicht: >>> a = ['hallo'] >>> b = ['hallo'] >>> a is b False
Obwohl beide Listen den gleichen Inhalt haben, werden sie als nicht identisch ausgewertet. Ein Test mit der Funktion id() zeigt nämlich Folgendes: >>> id(a) 11947384 >>> id(b) 11965064
Will man prüfen, ob zwei Objekte nicht identisch sind, geht das mit dem Operator is not.
Zugehörigkeit Um zu prüfen, ob ein Objekt in einer Sequenz oder Menge vorhanden ist, steht der Operator in zur Verfügung: >>> liste = ['Hut', 'Mantel', 'Hose', 'Jacke'] >>> 'Mantel' in liste True
Genauso kann das Vorhandensein von Teilsequenzen geprüft werden: >>> gruss = 'Hallo Welt!' >>> 'llo' in gruss True
74
3.4 Kontrollfluss
Die Umkehrung der Prüfung, also ob ein Objekt nicht in einer Sequenz vorhanden ist, funktioniert mit dem Operator not in: >>> liste = ['Hut', 'Mantel', 'Hose', 'Jacke'] >>> 'Hemd' not in liste True
Logische Operatoren Mit logischen Operatoren können Bedingungen verknüpft bzw. negiert werden. Die Negation wird mit dem Operator not erreicht: >>> a = 5 >>> a > 3 True >>> not a > 3 False
Wie man an dem Beispiel sieht, wird der Vergleichsoperator vorrangig vor dem notOperator ausgewertet. Zunächst wird der Vergleich durchgeführt und dann das Ergebnis negiert. Die logische Verknüpfung von Bedingungen kann entweder mit dem and-Operator (Konjunktion) oder dem or-Operator (Disjunktion) erfolgen. Bei der Konjunktion wird dann der Gesamtausdruck als True ausgewertet, wenn beide Bedingungen als True ausgewertet werden. Ist nur eine der beiden Bedingungen falsch, ist auch der Gesamtausdruck falsch: >>> a = 5 >>> a < 10 and a > 3 True >>> a < 10 and a < 3 False
Die folgende Tabelle zeigt die möglichen Ergebnisse einer Konjunktion: Bedingung 1
Bedingung 2
Bedingung 1 and Bedingung 2
True
True
True
True
False
False
False
True
False
False
False
False
Tabelle 3.5: Ergebnisse bei der Konjunktion
75
3 Die Struktur von Python
Bei der Disjunktion reicht es aus, wenn nur eine der beiden Bedingungen als True ausgewertet wird, damit der gesamte Ausdruck wahr ist. Erst wenn beide Bedingungen falsch sind, wird auch der Gesamtausdruck als falsch gewertet: >>> a = 10 >>> a > 10 or == 10 True >>> a > 10 or == 5 False
Die folgende Tabelle zeigt die möglichen Ergebnisse einer Disjunktion: Bedingung 1
Bedingung 2
Bedingung 1 or Bedingung 2
True
True
True
True
False
True
False
True
True
False
False
False
Tabelle 3.6: Ergebnisse bei der Disjunktion In einigen Fällen kann der or-Operator auch bei Zuweisungen verwendet werden, z. B. um bei Benutzereingaben einen Standardwert zu definieren: >>> alter = raw_input('Alter :') or 'keine Eingabe' Alter: >>> print alter keine Eingabe
3.4.3
Schleifen
Schleifen wiederholen einen Anweisungsblock entweder solange eine definierte Bedingung wahr ist oder für jedes Element einer Sequenz oder Menge. Im ersten Fall spricht man von einer bedingten Schleife, im zweiten von Iteration.
Bedingte Schleifen Die bedingte Schleife wird mit dem Schlüsselwort while eingeleitet und nach folgendem Muster gebildet: while Bedingung:
Anweisung(en)
Solange die Bedingung zutrifft, also als True ausgewertet wird, wird der Anweisungsblock wiederholt. Ein Beispiel, bei dem alle Quadratzahlen bis zu einer eingegebenen Zahl berechnet werden:
76
3.4 Kontrollfluss
ende = input('Berechne alle Quadratzahlen bis: ') b = 1 while b <= ende: print b**2 b+= 1
Das Skript erwartet eine Benutzereingabe. Diese wird in die Variable ende gespeichert. Dann wird eine zweite Variable mit dem Wert 1 belegt. Dies ist der Anfangswert für die Berechnung. Die Bedingung der Schleife trifft so lange zu, bis Variable b den eingegebenen Wert übersteigt. Im Anweisungsblock wird das Quadrat von b berechnet und mittels des print-Befehls ausgegeben. Entscheidend ist nun, dass b mit jedem Schleifendurchlauf um den Wert 1 erhöht wird. Geschähe dies nicht, würde die Schleife niemals beendet werden können und permanent den Wert 1 ausgeben. Durch die Erhöhung jedoch erreicht b irgendwann einmal den Wert der eingegebenen Zahl und die Bedingung wird falsch. Es ist bei bedingten Schleifen also unbedingt darauf zu achten, eine »Ausstiegsklausel« einzubauen, also dafür zu sorgen, dass die formulierte Bedingung auch wieder als False ausgewertet werden kann. Sollte es einem beim Testen in der Python-Shell einmal passiert sein, dass der Programmablauf in einer Endlosschleife gefangen ist, kann man durch die Tastenkombination (Strg) + (C) einen sogenannten KeyboardInterrupt auslösen, der das Programm abbricht.
Iteration Der Durchlauf einer Sequenz oder Menge wird mit dem Schlüsselwortpaar for ... in gebildet und nach folgendem Muster aufgebaut: for Variable in Sequenz:
Anweisung(en)
Dabei nimmt Variable nacheinander den Wert jedes einzelnen Elements in Sequenz an. Die Schleife endet automatisch, wenn alle Elemente der Sequenz durchlaufen wurden. Man muss sich also um die Beendigung der Schleife nicht kümmern. Ein Beispiel: tiere_liste = ['Hund', 'Katze', 'Pferd', 'Maus', 'Frosch'] for tier in tier_liste: print tier,
In der Ausführung gibt das Skript die Einträge in der Liste tier_liste mittels des print-Befehls aus: Hund Katze Pferd Maus Frosch
Die for-Schleife ist anwendbar auf Listen, Tupel, Zeichenketten, Mengen und Dictionarys. Bei einem Dictionary erfolgt die Iteration nur über die Schlüssel:
77
3 Die Struktur von Python
tier_dict = {'Hund':12, 'Katze':8, 'Pferd':15, 'Maus':1, 'Frosch':2} for tier in tier_dict: print tier,
Auch dies ergibt in der Ausführung: Hund Katze Pferd Maus Frosch
Die Werte im Dictionary erhält man auf diese Weise: for tier in tier_dict: print tier_dict[tier],
Jetzt erhält man: 8 12 15 1 2
Hat man verschachtelte Listen oder Tupel, kann man die Werte der inneren Sequenzen direkt ansprechen. Auch dazu ein Beispiel: preise_liste = (('Hemd', '39,90'), ('Hose', '69,90'), ('Jacket', '149,90'), ('Mantel', '259,90')) for produkt, preis in preisliste: print produkt, preis
Lässt man dieses Skript ablaufen, erhält man folgende Ausgabe: Hemd 39,90 Hose 69,90 Jacket 149,90 Mantel 259,90
Statt nur einer Variablen werden nun zwei definiert, die durch ein Komma getrennt werden. Im Schleifenkörper kann wie gewohnt auf jede der beiden Variablen zugegriffen werden. Hat man in den inneren Sequenzen mehr als zwei Werte, können auch weitere Schleifenvariablen hinzunotiert werden. Allerdings funktioniert dieser Zugriff auf die Werte der inneren Sequenzen nur dann, wenn die Datenstruktur konsistent ist. Haben die inneren Sequenzen unterschiedlich viele Elemente, ist diese Form des Zugriffs nicht anwendbar.
Zählschleifen In vielen Sprachen existieren eigene Konstrukte für Schleifen, die eine festgelegte Anzahl von Durchläufen haben und in denen die Schleifenvariable in einem bestimmten Intervall hoch- oder runtergezählt werden soll. In Python wird diese Anforderung sehr einfach mittels der for-Schleife und der Funktion range() gelöst.
78
3.4 Kontrollfluss
Diese Funktion liefert eine Liste mit Zahlen, über die iteriert werden kann. Als Parameter erhält die Funktion den Endwert der Liste: >>> range(6) [0, 1, 2, 3, 4, 5]
Wie man sieht, beginnen die Listeneinträge mit der Zahl 0 und enden mit dem Wert 5. Insofern ist die vorherige Aussage nicht ganz korrekt. Die Funktion erzeugt eine Liste mit den Zahlen von 0 bis n – 1, wobei n der eingegebene Parameter ist. In der Kombination mit der for-Schleife erhält man eine Zählschleife: for Variable in range(n) Anweisunge(en)
Die Schleifenvariable wird zu einem Zähler, der bei 0 beginnend mit jedem Schleifendurchlauf um den Wert 1 erhöht wird. Damit lässt sich das Beispiel der Potenzrechnung aus dem Abschnitt über bedingte Schleifen etwas eleganter formulieren: last = input('Berechne alle Quadratzahlen bis: ') for i in range(last): print (i+1)**2
Das Skript nimmt die vom Benutzer eingegebene Zahl und übergibt sie an die rangeFunktion. Da die Funktion eine Liste bildet, die mit 0 beginnt und mit dem Eingabewert –1 endet, wird vor der Potenzierung der Basis jeweils der Wert 1 hinzuaddiert. Man kann der range-Funktion auch direkt einen Startwert mitgegeben. Dieser wird als weiterer Parameter übergeben: >>> range(1, 6) [1, 2, 3, 4, 5]
Will man auf diese Weise die Berechnung der Potenzen durchführen, muss man das Skript folgendermaßen modifizieren: last = input('Berechne alle Quadratzahlen bis: ') for i in range(1, last+1): print (i)**2
Da nun die von range() gelieferte Liste mit dem Wert 1 beginnt, muss nur noch der Endwert um 1 erhöht werden, damit ein vom Benutzer erwartetes Ergebnis entsteht. Häufig kommt es vor, dass man nur jedes zweite Element einer Sequenz auslesen muss. Je nachdem, ob man die Elemente mit geradem oder ungeradem Index benötigt, kann man auch hierzu die range-Funktion einsetzen. Für die geraden Indexzahlen: >>> range(0,15,2) [0, 2, 4, 6, 8, 10, 12, 14]
79
3 Die Struktur von Python
Und für die ungeraden Indexzahlen: >>> range(1,16,2) [1, 3, 5, 7, 9, 11, 13, 15]
Der dritte Parameter, der an die Funktion übergeben werden kann, bestimmt die Schrittweite, mit der die Ergebnisliste gebildet wird. Hier sind natürlich auch andere Werte als 2 möglich: >>> range(10,101,10) [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
Es ist auch möglich, Listen zu erstellen, in die die Werte fallen. Dies wird mit einer negativen Schrittweite erreicht: >>> range(10,0,-1) [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
Die Funktion range() stellt also alles zur Verfügung, um in Kombination mit der forSchleife jede Art von Zählschleife zu bilden. Wenn man eine Schleife mit extrem vielen Durchläufen benötigt, kann es sehr viel Speicher kosten, wenn man mit range() arbeitet, weil diese Funktion immer eine Liste erzeugt. Alternativ kann man die Funktion xrange() verwenden. Sie funktioniert genauso wie range(), erzeugt aber keine Liste, sondern ein Objekt. Wenn es um Hunderttausende oder gar Millionen von Durchläufen geht und das Skript auf einem Rechner laufen muss, der weniger gut mit RAM ausgestattet ist, ist ein Objekt gegenüber einer Liste deutlich effizienter. Allerdings geht dieser Vorteil zu Lasten der Geschwindigkeit in der Verarbeitung, so dass man bei der Wahl der Mittel zwischen Speicheranforderung und Performance entscheiden muss. In 95% aller Anwendungsfälle dürfte range() die bessere Wahl sein.
Eingriffe in den Schleifenablauf Eine Schleife muss nicht um jeden Preis von A bis Z durchlaufen. Mit den Anweisungen continue und break hat man zwei Instrumente zur Hand, mit denen man in den Ablauf eingreifen kann. Dabei wird mit continue der aktuelle Schleifendurchlauf übersprungen, während break die Schleife komplett beendet. Zunächst ein Beispiel für die continue-Anweisung: seq = input('Bitte ein Zahlenfolge eingeben (Trennzeichen Komma): ') anzahl = 0 for zahl in seq: if zahl % 2: continue anzahl += 1 print 'Die Folge enthält', anzahl , 'gerade Zahlen.'
80
3.4 Kontrollfluss
Das Skript erwartet eine Zahlenfolge als Eingabe. Wenn die Zahlen mit Komma getrennt eingegeben werden, hat die Variable seq einen Tupel als Datentyp. Dieser wird mit einer for-Schleife durchlaufen. Für jedes Element wird eine Modulo-Rechnung (Division durch den Wert 2) vorgenommen. Handelt es sich um eine ungerade Zahl, bleibt ein Restwert von 1, die if-Bedingung wird also als True ausgewertet. In diesem Fall wird der Schleifendurchlauf abgebrochen und zum nächsten übergegangen. Ist es aber eine gerade Zahl, bleibt bei der Division durch 2 kein Restwert. Dann wird eine Zählervariable um den Wert 1 erhöht. Ist die Zahlenfolge komplett durchlaufen, wird das Ergebnis ausgegeben. Ein Skriptablauf: Bitte ein Zahlenfolge eingeben (Trennzeichen Komma) : 12,13,144,121,56 Die Folge enthält 3 gerade Zahlen.
Schließlich ein Beispiel für die break-Anweisung: seq = input('Zahlenfolge: ') for x in seq: if x >= 100: print 'Maximum enthalten!' break else: print 'Maximum nicht enthalten!'
Dieses Skript prüft, ob in einer Zahlensequenz ein Wert den Maximalwert (100) erreicht oder übersteigt. Ist das der Fall, wird eine entsprechende Ausgabe gemacht und die Schleife verlassen. Übersteigt keine Zahl den Maximalwert, wird ebenfalls eine entsprechende Ausgabe gemacht. Hier wird eine else-Klausel zur for-Schleife notiert. Diese wird dann ausgeführt, wenn die Schleife nicht mit break abgebrochen wurde. Auch while-Schleifen können eine solche else-Klausel enthalten. Ein Skriptablauf: Zahlenfolge: 1,2,3 Maximum nicht enthalten!
oder aber: Zahlenfolge: 99,100,101 Maximum enthalten!
Verschachtelte Schleifen Es ist möglich, in den Anweisungsblock einer Schleife wiederum eine Schleife zu notieren. Dadurch entstehen tabellenartige Strukturen, die schnell eine hohe Komplexität erreichen können. Die Schachteltiefe ist dabei nicht auf zwei Ebenen beschränkt.
81
3 Die Struktur von Python
Auch die innere Schleife kann wieder eine Schleife enthalten usw. Es wird natürlich mit jeder weiteren Ebene ungleich schwieriger, seine eigenes Konstrukt zu verstehen. Hier ein einfaches Beispiel, das Obst an Kinder (virtuell) verteilt: kinder = ['Moni','Berti','Lisa','Hans',] obst = ['Apfel','Birne','Banane',] for kind in kinder: print kind, 'bekommt:', for frucht in obst: print frucht, print ''
Für jedes Kind aus der Liste kinder wird die Liste Obst durchlaufen, so dass jedes Kind aus »Obstkorb« sein Stück bekommt. Die print-Anweisung in der letzten Zeile dient lediglich der Erzeugung eines Zeilenumbruchs. Im Ablauf sieht das dann so aus: Moni bekommt: Apfel Birne Banane Berti bekommt: Apfel Birne Banane Lisa bekommt: Apfel Birne Banane Hans bekommt: Apfel Birne Banane
3.5
Ausnahmen behandeln
Beim Programmieren denkt man nicht gern an Fehler, zumal man schon einen Großteil der Zeit mit der Fehlerbehebung zu tun hat. Aber Programme sind Werkzeuge, die von Menschen verwendet werden, also kann es auch zu einer fehlerhaften Benutzung kommen. Python unterstützt eine strukturierte Behandlung von Fehlern. Wenn der Interpreter einen solchen feststellt, wird eine Ausnahme ausgelöst. Diese kann mit einigen Mitteln behandelt werden, die im Folgenden beschrieben werden.
3.5.1
Ausnahmen abfangen
Einige der vorangegangenen Beispiele haben mit der input-Funktion gearbeitet. Diese wandelt die Eingabe des Benutzers automatisch in einen Datentyp, der der Eingabe entspricht. Das kann aber im weiteren Verlauf zu Problemen führen, weil die Eingabe nicht dem erwarteten Datentyp entspricht. Wird etwa in einem Skript eine Berechnung durchgeführt, aber eine Zeichenkette eingegeben, kann es im Skript zu einem Fehler (Ausnahme) kommen, was dann zu einem Abbruch des Skriptablaufs führt. Einen solchen Abbruch sollte man unbedingt vermeiden. Stürzt ein Programm ab, bevor Daten gesichert wurden, ist unter Umständen eine Menge Arbeit umsonst gewesen. Dies kann auch Kosten verursachen, die eigentlich nicht geplant waren. Handelt es sich bei der Software um ein Steuerungsprogramm, das für das korrekte Arbeiten einer Maschine oder ganzen Anlage zuständig ist, können die Folgen eines Programmabsturzes fatal sein. Aus diesem Grund versucht man Fehler, die Programmabbrüche
82
3.5 Ausnahmen behandeln
verursachen können, abzufangen und stattdessen Alternativen auszuführen. In Python wird dieses mit der try ... except-Anweisung gemacht. Sie wird nach folgendem Muster aufgebaut: try: anweisung(en) except [Fehlertyp]: alternative(n)
Mit einem Beispiel soll die Funktionsweise erläutert werden: while True: # endlose Schleife try: zahl = int(input('Zahl: ')) break #Schleife wird verlassen except: print 'Bitte eine korrekte Zahl eingeben!' for x in range(zahl): if x%5 == 0: print # erzeugt Umbruch print x,
Das Skript gibt eine Zahlenreihe aus, deren Länge von der Benutzereingabe abhängig ist. Der try ... except-Block ist hier in einer Schleife untergebracht, die so lange läuft, bis eine korrekte Zahl eingegeben wurde. Im try-Block wird versucht, die Eingabe des Benutzers mittels der Funktion int() in eine Ganzzahl zu wandeln. Danach wird mit einer break-Anweisung die Schleife verlassen und es kann die for-Schleife zur Ausgabe der Zahlen ausgeführt werden. Kommt es aber zu einem Fehler im try-Block, wird die Ausführung des Blocks unterlassen und stattdessen der except-Block ausgeführt. Das ist in diesem Fall eine print-Anweisung, die auf den Fehler aufmerksam macht. Da das Ganze in einer while-Schleife stattfindet und im Fehlerfall keine breakAnweisung gegeben wird, kommt es zur erneuten Ausführung der input-Anweisung. Ein Skriptablauf mit falscher Eingabe sieht folgendermaßen aus: Zahl: 'hallo' Bitte eine korrekte Zahl eingeben! Zahl:
Wenn eine korrekte Zahl eingegeben wird, ist der Ablauf so: Zahl: 20 0 1 2 5 6 7 10 11 15 16
3 4 8 9 12 13 14 17 18 19
Mithilfe der näheren Bestimmung von Fehlertypen kann das Skript noch differenzierter auf die Benutzereingaben reagieren:
83
3 Die Struktur von Python
while True: try: zahl = int(input('Zahl: ')) break except NameError: print 'nur Ziffern eingeben' except ValueError: print 'nicht wandelbar' except: print 'Bitte eine korrekte Zahl eingeben!' for x in range(zahl): if x%5 == 0: print # erzeugt Umbruch print x,
Je nachdem, welche Eingabe der Nutzer macht, kann es zu unterschiedlichen Fehlern kommen. Gibt er z. B. eine Zeichenkette ein, die Buchstaben enthält, so wird ein ValueError ausgelöst. Besteht die Eingabe nur aus Buchstaben (nicht in Anführungszeichen), wird ein NameError ausgelöst. In Anhang B sind die Fehlertypen, die Python auslösen kann, näher beschrieben. Wird bei der Umwandlung der Eingabe in eine Ganzzahl ein Fehler ausgelöst, der nicht diesen beiden Fehlertypen entspricht, greift die except-Anweisung ohne Fehlertypangabe. Damit ist man also auf der sicheren Seite, wenn man einzelne Fehlertypen nicht explizit benannt hat.
3.5.2
Vor dem Abbruch aufräumen
Ein anderes Konzept des Umgangs mit Fehlern ist es, zwar den Programmabbruch zuzulassen, vorher aber wichtige Aufräumarbeiten in jedem Fall noch durchzuführen. So kann man z. B. noch Daten in eine Datei schreiben oder eine Datenbankverbindung schließen, bevor das Programm abstürzt. In Python erreicht man dies mit einer try ... finally-Anweisung: try: Anweisung(en) finally: Anweisung(en)
Mit einem Beispiel soll auch dieses Konzept erläutert werden: datei = open(r'c:\daten\test.txt', 'w') try: datei.write('test') finally: datei.close()
In diesem Beispiel wird eine Datei geöffnet und anschließend im try-Block versucht, etwas in sie hineinzuschreiben. Schlägt dies fehl, wird – bevor der Fehler ausgelöst wird – die Datei über den finally-Block geschlossen.
84
3.6 Funktionen
3.6
Funktionen
Funktionen erlauben es, Teilaufgaben eines Programms in separate Einheiten auszulagern und bei Bedarf aufzurufen. Man unterscheidet zwischen Funktionen, die einen Wert zurückgeben, und solchen, die dies nicht tun. Letztere werden auch als Prozeduren bezeichnet. In Python geben Funktionen, die keinen Rückgabewert liefern, None zurück. In den bisherigen Beispielen wurden bereits einige Funktionen verwendet. Sie gehören ausnahmslos zu den in Python enthaltenen Standardfunktionen (builtin functions). So sind z. B. die Funktionen str() oder int() wie auch die übrigen Funktionen zur Typenumwandlung (Abschnitt 3.2.8) solche Standardfunktionen. (Eine Liste der Standardfunktionen ist in Anhang A zu finden.) Darüber hinaus bringen noch einige der importierbaren Module Funktionen mit.
3.6.1
Funktionsaufrufe
Funktionen besitzen Namen, über die sie aufgerufen werden. Die einfachste Form des Funktionsaufrufs ist: funktionsname()
Es wird einfach nur der Name der Funktion gefolgt von einem leeren Klammerpaar angegeben. Mit dem Aufruf der Funktion ist die Ausführung des Codes im Funktionskörper verbunden. Die Ausführung hat in der Regel die Rückgabe eines Werts zur Folge: >>> int() 0
Bei der Funktion int() ist der Rückgabewert 0, wenn man ihr keinen Parameter übergibt. Über Parameter werden einer Funktion Daten übergeben, die im Funktionskörper verarbeitet werden. Allgemein sieht ein solcher Aufruf folgendermaßen aus: funktionsname(parametersequenz)
Je nachdem, was die Funktionsdefinition vorsieht, können ein oder mehrere Parameter übergeben werden: >>> len('hallo') 5
Die Funktion len() ermittelt die Anzahl der Elemente einer Sequenz und gibt diese als Ganzzahl zurück. Übergibt man ihr keinen oder mehr als einen Parameter, wird ein Fehler ausgelöst:
85
3 Die Struktur von Python
>>> len('hallo', 'Welt') Traceback (most recent call last): File "", line 1, in <module> len('hallo', 'Welt') TypeError: len() takes exactly one argument (2 given)
Der Hinweis im Traceback ist eindeutig: Die Funktion verlangt genau einen Parameter (argument), aber es wurden zwei übergeben. Hinweis Traceback übersetzt man im Deutschen am ehesten mit Rückverfolgung. In Python wird so die Fehlerausgabe bezeichnet, die anzeigt, in welchem Skript (Modul), welcher Zeile und welchem Ausdruck der Fehler entstanden ist. Dies ist ein erster wichtiger Anhaltspunkt für die Fehlersuche. Die angegebenen Stellen im Skript sind aber nicht in jedem Fall die Stellen, wo die Ursache des Fehlers zu finden ist. Wenn man mehrere Parameter übergibt, so werden diese durch Komma getrennt: >>> int('101010',2) 42
Funktionen sind Objekte, denen man folglich auch Variablen zuweisen kann. Dadurch wird es möglich, sie unter einem anderen Namen aufzurufen: >>> ganzzahl = int >>> ganzzahl('120') 120
Genauso ist es aber auch möglich, sie in Listen oder Dictionarys abzulegen: >>> funktionen = {'ganzzahl': int, 'zeichekette' : str} >>> funktionen['zeichenkette'](943) '943'
Entsprechend ändert sich dann natürlich auch der Aufruf einer Funktion.
3.6.2
Funktionen definieren
Die Definition eigener Funktionen wird immer mit dem Schlüsselwort def eingeleitet. Sie geschieht nach dem folgenden Modell: def funktionsname(parametersequenz): anweisung(en)
Die erste Zeile wird auch als Funktionskopf bezeichnet, während man die Anweisungen auch Funktionsblock nennt. Dieser Block muss eingerückt werden, damit er als zur Funktion gehörig erkannt wird. Einen Funktionskopf ohne -block darf es nicht
86
3.6 Funktionen
geben. Dies quittiert Python mit einer Fehlermeldung. Bei der Vergabe von Funktionsnamen ist man an die Regeln gebunden, die bereits in Abschnitt 2.1.4 erläutert wurden. Ein Beispiel soll das Aufbauen einer Funktion verdeutlichen: Listing 3.1: Eine einfache Funktion 01 def aritmetic(x,y,op): 02 if op == 'addition': 03 ergebnis = x + y 04 elif op == 'subtraktion': 05 ergebnis = x - y 06 elif op == 'multiplikation': 07 ergebnis = x * y 08 elif op == 'division': 09 ergebnis = x/y 10 else: 11 ergebnis = 'keine Rechenoperation angewendet.' 12 return ergebnis
Diese einfache Funktion kann mit zwei Zahlen (x und y) eine der vier Grundrechenarten ausführen. Welche Rechenart ausgeführt wird, steuert der Parameter op. In Zeile 01 wird also zunächst der Kopf definiert. Die Funktion erhält den Namen aritmetic und drei Parameter. Im Funktionskörper werden diese Parameter wie Variablen behandelt. Ihre Werte erhalten sie beim Aufruf der Funktion. Mit einer if-elif-elseKonstruktion wird geprüft, welchen Wert der Parameter op bekommen hat. Entsprechend des Werts wird eine der Rechenarten ausgeführt und das Ergebnis in der gleichnamigen Variablen gespeichert. Mit der return-Anweisung in Zeile 12 schließlich wird der Wert dieser Variablen zurückgegeben. Mit dieser Definition der Funktion passiert natürlich noch nichts. Ruft man es als Modul in der Python-Shell auf, sieht man keine Auswirkungen. Die Funktion beginnt erst dann zu »arbeiten«, wenn man sie explizit aufruft: >>> aritmetic(5,4, 'addition') 9
Der Code im Funktionskörper wird ausgeführt und am Ende wird das Ergebnis zurückgegeben. Wie bei den Standardfunktionen ist auch hier darauf zu achten, dass die Funktion für jeden Parameter Werte erhält, andernfalls wird ein Fehler ausgelöst: >>> aritmetic() Traceback (most recent call last): File "", line 1, in <module> aritmetic() TypeError: aritmetic() takes exactly 3 arguments (0 given)
87
3 Die Struktur von Python
Die Rückgabe des Werts muss nicht unbedingt an die Ausgabe der Python-Shell erfolgen. Genauso kann auch eine Variable den Wert übernehmen: >>> a = aritmetic(5,4, 'subtraktion') >>> a 1
3.6.3
Namensräume
Bei der Arbeit mit Funktionen muss man darauf achten, wo Variablen definiert werden. Nicht in jedem Fall steht eine im Hauptprogramm definierte Variable auch in einer Funktion zur Verfügung. Eine Funktion ist eine in sich geschlossene Einheit, die einen eigenen sogenannten Namensraum besitzt. Als Namensraum bezeichnet man die Gesamtheit aller Namen (Bezeichner für Objekte), die im jeweiligen Kontext zur Verfügung stehen. Innerhalb einer Funktion stehen nicht alle Namen automatisch aufrufbar und auch umgekehrt ist der Namensraum einer Funktion von anderen Funktionen und vom Hauptprogramm abgegrenzt. Gleichwohl sind die verschiedenen Namensräume durchlässig genug, um einen effektiven Datenaustausch zu gewährleisten. Ein einfaches Beispiel zeigt das: Listing 3.2: Beispiel für unterschiedliche Namensräume 01 02 03 04 05 06 07 08 09
In der Funktion ausgabe() wird eine Variable wort deklariert und mit dem print-Befehl ausgegeben. Im Hauptprogramm wird zunächst die Funktion aufgerufen und dann ebenfalls eine Variable wort gebildet und ausgegeben. Lässt man das Skript ablaufen, erhält man folgendes Ergebnis: Monty Python
Zuerst wird durch den Funktionsaufruf die Variable aus der Funktion ausgegeben und dann die Variable des Hauptprogramms in die Shell geschrieben. Obwohl die Variable der Funktion den gleichen Namen trägt wie die Variable im Hauptprogramm, überschreibt sie diese nicht. Vielmehr wird sie im Namensraum der Funktion gebildet, während der Namensraum im Hauptprogramm unangetastet bleibt. Man unterscheidet hier zwischen dem globalen Namensraum (des Hauptprogramms) und
88
3.6 Funktionen
dem lokalen Namensraum der Funktion. Man kann sich die Inhalte der jeweiligen Namensräume mithilfe zweier Funktionen ausgeben lassen. Es sind die Funktionen globals() für den globalen Namensraum und locals() für den lokalen Namensraum. Wendet man sie auf das vorherige Beispiel an, erhält man folgendes Skript: Listing 3.3: Ausgabe der Namensräume 01 02 03 04 05 06 07 08 09 10 11 12
Statt der Ausgabe der Variable, wird nun in der Funktion und im Hauptprogramm jeweils der globale und der lokale Namensraum ausgegeben. Die leere print-Anweisung in Zeile 10 dient lediglich dazu, einen Zeilenumbruch zu erzeugen. In der Ausgabe erhält man nach Ablauf des Skripts Folgendes: Funktion, global {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Python25\\Lib\\idlelib\\idle.pyw', 'idlelib': <module 'idlelib' from 'C:\Python25\lib\idlelib\__init__.pyc'>, 'ausgabe': , 'wort': 'Python', '__name__': '__main__', '__doc__': None} Funktion, lokal {'wort': 'Monty'} Hauptprogramm, global {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Python25\\Lib\\idlelib\\idle.pyw', 'idlelib': <module 'idlelib' from 'C:\Python25\lib\idlelib\__init__.pyc'>, 'ausgabe': , 'wort': 'Python', '__name__': '__main__', '__doc__': None} Hauptprogramm, lokal {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Python25\\Lib\\idlelib\\idle.pyw', 'idlelib': <module 'idlelib' from 'C:\Python25\lib\idlelib\__init__.pyc'>, 'ausgabe': , 'wort': 'Python', '__name__': '__main__', '__doc__': None}
Man sieht, dass der Inhalt eines Namenraums als Dictionary ausgegeben wird. Vergleicht man die Ausgabe des globalen Namensraums aus der Funktion und aus dem Hauptprogramm, so sieht man, dass sie identisch sind. In beiden finden sich einige Standardnamen des globalen Namensraums sowie die Namen ausgabe und wort, also die Namen der Funktion und der Variablen. Unterschiedlich allerdings sind die jeweiligen Ausgaben des lokalen Namensraums. Im Hauptprogramm ist der lokale mit dem globalen Namensraum identisch. Der
89
3 Die Struktur von Python
lokale Namensraum der Funktion enthält hingegen nur den Namen wort. Und wie man sieht, als Wert Monty, während der Wert der Variablen wort im globalen Namensraum Python ist. Man sieht hier, dass man es mit zwei unterschiedlichen Variablen zu tun hat, die zufällig den gleichen Namen haben. Nimmt man sich jetzt noch mal das erste Beispiel vor und ändert in der Funktion nur eine Zeile, bekommt man ein neues Ergebnis: Listing 3.4: Modifizierte Funktion 01 02 03 04 05 06 07 08 09
Mit dem Auskommentieren der Zeile 03 wird in der Funktion nun keine Variable mehr deklariert. Die Ausgabe sieht dann folgendermaßen aus: Python Python
Jetzt gibt auch die Funktion die Variable aus dem globalen Namensraum aus. Da keine Variable mit dem Namen wort in den lokalen Namensraum geschrieben wird, ist jetzt quasi der Weg frei, den Namen im globalen Namensraum zu suchen. In der Tat ist es so, dass Namen zuerst im lokalen Namensraum gesucht werden. Ist der gesuchte Name dort nicht vorhanden, wird im globalen Namensraum gesucht. Mit einer weiteren Änderung erhält man noch mal ein anderes Ergebnis: Listing 3.5: Eingefügte global-Anweisung 01 02 03 04 05 06 07 08 09 10
Jetzt wird wieder die Variable wort mit einem Wert versehen. Zuvor allerdings wurde mit der global-Anweisung der Bezug zum Namen aus dem globalen Namensraum hergestellt. Die Ausgabe ist jetzt: Monty Monty
Wenn man also eine Variable aus dem Hauptprogramm verändern will, muss man die Referenz auf diese Variable mit der global-Anweisung herstellen. Dann ist es der Funktion möglich, die ursprüngliche Zuweisung Python durch Monty zu überschreiben. Es ist innerhalb einer Funktion nicht nur möglich, vorhandene Variablen aus dem Hauptprogramm so zu modifizieren, es können auch neue Variablen so in den globalen Namensraum geschrieben werden: >>> def test(): global a a = 1 print globals()
Weil auch in der Testfunktion die global-Anweisung notiert ist, bevor der Variablen a ihr Wert zugewiesen wird, schreibt die Funktion den Namen in den globalen Namensraum. Die Ausgabe mittels der globals-Funktion zeigt dies. Will man mehrere Variablen aus dem globalen Namensraum ansprechen, so kann man sie hinter der global-Anweisung durch Kommata getrennt angeben: global a, b, c, d
3.6.4
Parameter und ihre Übergabe
Parameter und Datentypen In den bisherigen Beispielfunktionen wurde bereits mit Parametern operiert. Diese sollen jetzt etwas genauer betrachtet werden. Zunächst soll der Blick auf die Übergabe von verschiedenen Datentypen an eine Funktion fallen. Dazu zunächst eine einfache Funktion: def verdoppeln(zahl): zahl * 2 return zahl
91
3 Die Struktur von Python
Die Funktion verdoppelt den Wert, der für den Parameter zahl übergeben wird, und gibt das Ergebnis zurück. Wenn man nun an diese Funktion einen Wert mittels einer Variablen übergibt, ist es interessant zu wissen, was im Anschluss mit dieser Variablen passiert: >>> i = 5 >>> verdoppeln(i) 10 >>> i 5
Die Funktion nimmt die Variable und übergibt ihren Wert an den Parameter zahl. Sie liefert dann das erwartete Ergebnis. Die Variable i behält dabei ihren ursprünglichen Wert. Man spricht hier davon, dass Variable und Funktion bzw. Parameter keine Bindung haben. Dies ist immer dann der Fall, wenn an eine Funktion nicht änderbare Datentypen übergeben werden. Anders verhält es sich, wenn man änderbare Datentypen, also Listen oder Dictionarys, übergibt: def verdoppeln2(dic): for i in dic: dic[i] = dic[i] * 2 return dic
Diese Funktion nimmt sich jedes Schlüssel-Werte-Paar eines Dictionary vor und verdoppelt den jeweiligen Wert. Als Ergebnis wird das veränderte Dictionary zurückgegeben. Übergibt man nun ein Dictionary, erhält man folgenden Ablauf: >>> zahlen = {'eins' : 1, 'zwei' : 2, 'drei' : 3} >>> verdoppeln2(zahlen) {'drei': 6, 'eins': 2, 'zwei': 4} >>> zahlen {'drei': 6, 'eins': 2, 'zwei': 4}
Der Funktionsaufruf liefert wieder das erwartete Ergebnis. Alle Werte im Dictionary wurden verdoppelt. Aber dieses Mal hat die Veränderung auch das ursprüngliche Dictionary mit dem Namen zahl erfasst. Zwischen Variablen und der Funktion bzw. dem Parameter besteht eine Bindung, die bewirkt, dass jede Änderung an dem Parameter auch in der Variablen nachvollzogen wird. Diesen Umstand sollte man unbedingt beachten, wenn man veränderbare Datentypen (also auch Listen!) an eine Funktion übergibt. Etwas später wird noch gezeigt, dass man die ursprüngliche Variable auch unverändert halten kann, wenn in einer Funktion Änderungen vorgenommen werden.
Wertübergaben Wenn eine Funktion mehrere Parameter hat, dann ist beim Funktionsaufruf die Reihenfolge, in der die Werte übergeben werden, entscheidend. Auch hierzu ein Beispiel: def abziehen(a, b): return a – b
92
3.6 Funktionen
Wenn man beim Aufruf dieser Funktion die zu übergebenden Werte vertauscht, erhält man verschiedene Ergebnisse: >>> abziehen(10, 3) 7 >>> abziehen(3, 10) -7
Die Zuordnung der Werte zu den Parameternamen erfolgt über die Position in der Parametersequenz. Der erste Wert wird dem ersten Parameter, der zweite Wert dem zweiten Parameter zugeordnet. Man kann aber die einzelnen Parameter direkt adressieren, indem man ihre Namen bei der Werteübergabe verwendet: >>> abziehen(b=3, a=10) 7
Bei dieser Art der Werteübergabe spielt die Reihenfolge der Argumente keine Rolle mehr, da die Zuordnung über den Namen des Parameters eindeutig ist. Diese Form der Werteübergabe wird Schlüsselwortübergabe (keyword passing) genannt. Man kann den Parametern in der Funktionsdefinition bereits Werte zuweisen. Diese Werte werden als Standardwerte verwendet, wenn der Funktion oder einzelnen Parametern keine Werte übergeben werden: def abziehen(a=0, b=0): return a – b
Diese Form der Parameter werden Schlüsselwortargumente genannt. Ruft man die Funktion nun auf, ohne ihr Werte zu übergeben, werden die Standardwerte verwendet: >>> abziehen() 0
Mögliche andere Aufrufe dieser Funktion sind: >>> abziehen(3) 3
Hier wird dem ersten Parameter, also a, der Wert 3 übergeben, für b wird der Standardwert verwendet. >>> abziehen(5,1) 4
Wie gewohnt erhalten die Parameter ihre Werte anhand ihrer Position in der Parametersequenz. >>> abziehen(b=7) -7
93
3 Die Struktur von Python
Bei diesem Aufruf wird für a der Standardwert verwendet, während an b der Wert 7 übergeben wird. Mit Schlüsselwortargumenten und der Schlüsselwortübergabe erhält man die größte Flexibilität bei der Verwendung von Funktionen. Es kann zu keinem Fehler wegen fehlender Parameterwerte kommen und die Wertzuordnung zu den Parametern ist eindeutig geregelt.
Unbestimmte Anzahl an Parametern Es kann Situationen geben, in denen es notwendig wird, dass man an eine Funktion eine variable Anzahl an Parametern übergeben kann. Dies kann zum Beispiel bei Benutzereingaben der Fall sein, bei denen man nicht kontrollieren kann, wie viele Werte eingegeben werden. In Python kann man eine unbestimmte Anzahl Parameter verarbeiten, die anhand ihrer Position oder per Schlüsselwort übergeben werden. Ein Beispiel für Parameter, die nach Position zugeordnet werden: def ausgabe(*daten): for item in daten: print item
Diese Funktion gibt die ihr übergebenen Werte in der Python-Shell aus. Einziger Parameter ist daten. Die Kennzeichnung mit dem Sternsymbol (*) macht aus dem Parameter daten ein Tupel, das alle übergebenen Werte aufnimmt. Auf diesen Tupel kann im Funktionskörper z. B. eine for-Schleife angewendet werden. Ein möglicher Aufruf der Funktion: >>> ausgabe('Hans','Klaus','Sigrid') Hans Klaus Sigrid
Man sieht, dass die Ausgabe der Werte der Reihenfolge ihrer Übergabe entspricht. Es bleibt also bei der Zuordnung anhand der Position. Bei der variablen Anzahl von Schlüsselwortargumenten wird der aufnehmende Parameter mit zwei Sternen (**) gekennzeichnet: def ausgabe2(**daten): for item in daten: print item,': ', daten[item]
Der Parameter daten sammelt hier die übergebenen Schlüssel-Werte-Paare in einem Dictionary, das im Funktionskörper entsprechend ausgewertet werden kann: ausgabe2(vorname='Hans', nachname='Mustermann', alter='36') nachname : Mustermann vorname : Hans alter : 36
94
3.6 Funktionen
Vermischte Übergaben Man ist nicht auf eine Form der Parameterdefinition beschränkt. Man kann alle Formen mischen, muss sich aber an zwei Regeln halten: Bei der Wertübergabe müssen Positionsargumente vor Schlüsselwortargumenten
notiert werden. In der Parametersequenz müssen Parameter, die eine variable Anzahl von Werten
aufnehmen sollen, am Ende stehen. Daraus folgt, dass bei Verwendung aller vier Parameterformen nur folgende Reihenfolge möglich ist: def mixed(a,b,c='key',*d,**e): print a,b print c print d print e
Zuerst werden die Positionsparameter notiert, gefolgt von den Schlüsselwortparametern. Danach kommt der Parameter für eine variable Anzahl von Positionsargumenten, gefolgt vom Parameter für die variable Anzahl von Schlüsselwortargumenten. Beim Aufrufen allerdings kann für den Parameter c kein Schlüsselwortargument verwendet werden, weil sonst die erste Regel verletzt würde. Eine solche Anordnung von Parametern ist also nur theoretisch von Bedeutung. Ein möglicher Funktionsaufruf: >>> mixed(1,2,'schluessel',9,8,7,6,ha=111,hb=222) 1 2 schluessel (9, 8, 7, 6) {'hb': 222, 'ha': 111}
Man sieht, wie die ersten drei Werte von den Parametern a, b und c aufgenommen werden. Die noch verbleibenden Positionsargumente (9, 8, 7, 6) werden vom Parameter d verarbeitet, während die beiden Schlüsselwortargumente vom Parameter e übernommen werden.
3.6.5
Lokale Funktionen
Es ist möglich, innerhalb einer Funktion weitere Funktionen zu definieren. Dadurch können wiederkehrende Teilaufgaben einer Funktion definiert werden. Solche Funktionen werden lokale Funktionen genannt, weil sie zum Namensraum der Hauptfunktion gehören und vom Hauptprogramm aus nicht aufgerufen werden können. Das folgende Beispiel berechnet die Summe der Quersummen einer beliebigen Anzahl von Zahlen:
95
3 Die Struktur von Python
Listing 3.6: Lokale Funktion zur Berechnung der Quersumme 01 def quersummen(*zahlen): 02 def quer(n): # einzelne Quesumme berechnen 03 qs = 0 04 for i in str(n): 05 qs += int(i) 06 return qs 07 08 ergebnis = 0 09 for zahl in zahlen: 10 ergebnis += quer(zahl) # Quersummen addieren 11 return ergebnis
Wenn die Summe von Quersummen berechnet werden soll, muss zunächst von jeder eingegebenen Zahl die Quersumme berechnet werden. Diese Aufgabe ist in die lokale Funktion quer (Zeilen 02–06) ausgelagert. Die übergebene Zahl wird in eine Zeichenkette umgewandelt, so dass man die Möglichkeit hat, jede Ziffer einzeln zu erfassen (Zeile 04) und zu addieren (Zeile 05). In Zeile 10 des Skripts wird die lokale Funktion für jede Zahl ausgeführt. Die von ihr zurückgegebene Quersumme wird zum Wert der Variablen ergebnis hinzuaddiert. Ein möglicher Funktionsaufruf: >>> quersummen(11,22,33,44,55) 30
Gibt man in der Python-Shell die Funktion globals() ein, dann sieht man, dass die Funktion quer() nicht im globalen Namensraum vorhanden ist, wohl aber die Funktion quersummen(): >>> globals() {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Python25\\Lib\\idlelib\\idle.pyw', 'idlelib': <module 'idlelib' from 'C:\Python25\lib\idlelib\__init__.pyc'>, 'quersummen': , '__name__': '__main__', '__doc__': None}
Fügt man in die Funktion quersummen() die Zeile print locals()
vor der return-Anweisung ein und ruft sie erneut auf, sieht man, dass die Funktion quer zum lokalen Namensraum gehört: >>> quersummen() {'quer': , 'zahlen': (), 'ergebnis': 0} 0
96
3.6 Funktionen
3.6.6
Rekursion
Eine Funktion, die sich selbst aufruft, wird als rekursiv bezeichnet. Ähnlich der whileSchleife, wird der Funktionskörper mehrfach ausgeführt und die Daten werden bei jedem Durchlauf modifiziert. Ein Beispiel: Listing 3.7: Rekursive Funktion 01 def countdown(zahl): 02 if zahl > 0: 03 print zahl 04 countdown(zahl-1) 05 else: 06 print 'take off!'
Die Funktion nimmt eine Ganzzahl und zählt diese bis 1 herunter. Wird der Wert 0 erreicht, wird die Zeichenkette take off! ausgegeben. In Zeile 04 des Skripts erfolgt der Selbstaufruf mit der um den Wert 1 reduzierten Zahl. Ein Aufruf der Funktion hat folgendes Ergebnis: >>> countdown(5) 5 4 3 2 1 take off!
Wie bei der while-Schleife kommt es darauf an, im Funktionskörper eine Bedingung zu formulieren, die den Funktionsaufruf stoppt, andernfalls kommt es zu einer unendlichen Rekursion. Tatsächlich löst der Python-Interpreter nach einer vordefinierten Anzahl von Rekursionen einen Fehler aus. Diese Zahl wird als maximale Rekursionstiefe bezeichnet. Hintergrund dieser Beschränkung ist, dass mit jedem Funktionsaufruf ein Objekt erzeugt wird, das den aktuellen Ausführungsstand repräsentiert, man spricht auch von einem Execution Frame. Jedes Objekt belegt Platz im Arbeitsspeicher, so dass es bei einer zu hohen Rekursionstiefe zu einem Überlauf des Speichers kommen kann. Ein kleiner Test belegt, das nach nicht mal 1000 Rekursionen eine Fehlermeldung ausgegeben wird: Listing 3.8: Nie endender rekursiver Funktionsaufruf def zu_weit(n): print n zu_weit(n+1)
97
3 Die Struktur von Python
Führt man diese Funktion aus, erhält man folgende Ausgabe: >>> zu_weit(1) 1 2 3 ... 986 987 Traceback (most recent call last): File "", line 1, in <module> zu_weit(1)
Da kein Ausstieg aus der Rekursion in der Funktion vorhanden ist, kommt es zur »ewigen« Ausführung, die aber nach 987 Durchläufen vom Interpreter gestoppt wird. Damit ist auch schon ein wesentlicher Nachteil von rekursiven Funktionen benannt: der hohe Speicherbedarf zur Speicherung der Zwischenergebnisse der Funktionsaufrufe. Ein weiterer Nachteil ist die Geschwindigkeit, mit der Funktionen ausgeführt werden. Häufig erreicht man das gleiche Ergebnis mit einer Schleife schneller. Ein Vorteil von rekursiven Schleifen gegenüber »klassischen« Schleifen entsteht allerdings dann, wenn parallel zur Schleife weitere Prozesse ausgelöst werden und ablaufen müssen. Schleifen werden in der Regel bis zum Ende durchgearbeitet, so dass es schwierig werden kann, Parallelprozesse auszuführen. Befindet sich der Programmablauf aktuell in einer Schleife und vorher wurde z. B. eine Anfrage an eine Datenbank gesendet, kann die Antwort der Datenbank nicht empfangen werden
3.6.7
Lambda-Funktionen
Mit der Lambda-Form ist es möglich, kompakte, anonyme Funktionen zu definieren. Dies geschieht nach folgendem Modell: lamba Parametersequenz: Ausdruck
Für den Bereich rechts vom Doppelpunkt gibt es allerdings Einschränkungen. Es können nur Ausdrücke verwendet werden, so dass Variablendeklarationen und der print-Befehl ausscheiden. Das folgende Beispiel errechnet direkt die Fläche eines Kreises mittels einer Lambda-Funktion: >>>(lambda r: r**2*3.14159)(10) 314.15899999999999
Bei dieser direkten Ausführung der Funktion wird die gesamte Funktionsdefinition in Klammern notiert. In der anschließenden Klammer wird der Wert für den Parameter übergeben. Es ist genauso möglich, die Funktion einer Variablen zuzuweisen: >>> kreisflaeche = lambda r: r**2*3.14159
98
3.6 Funktionen
Ein Aufruf der Funktion erfolgt dann über die Variable: >>> kreisflaeche(5) 78.539749999999998
Mehrere Parameter werden bei der Definition und beim Aufruf wie gewohnt durch Komma getrennt: >>> zylindervolumen = lambda r,h: r**2*3.14159*h >>> zylindervolumen(h=10, r=5) 785.3975
Diese Funktion berechnet das Volumen eines Zylinders und benötigt als Werte den Radius und die Höhe des Zylinders. Wie man sieht, sind auch Schlüsselwortübergaben möglich. Für Lambda-Funktionen gelten in Bezug auf Parameter die gleichen Regeln wie für normale Funktionen. So ist es auch möglich, eine variable Anzahl von Parametern zu übergeben: >>> mittel = lambda *zahlen: float(sum(zahlen))/len(zahlen) >>> mittel(2,3,6,8,11,12,15,17,18,21) 11.3
Diese Funktion berechnet den Mittelwert einer Zahlensequenz. Die Übergabe der Werte erfolgt an den Parameter zahlen, der rechts vom Doppelpunkt – also in der Funktion – als Tupel behandelt wird. Jetzt muss nur noch die Summe aller Zahlen berechnet werden, was die Funktion sum() übernimmt. Das Ergebnis wird mit der float-Funktion in eine Fließkommazahl umgewandelt und schließlich durch die Anzahl der Zahlen geteilt. Diese Anzahl wird mit der len-Funktion ermittelt. Dieses letzte Beispiel zeigt sehr schön, wie man mithilfe von Lambda-Funktionen Aufgaben kompakt lösen kann. Sie eignen sich u. a. sehr gut für den Einsatz als lokale Funktion, um Teilaufgaben zu übernehmen.
3.6.8
Docstrings und kommentierte Parameter
In Python kann man innerhalb einer Funktion eine Zeichenkette platzieren, die der Dokumentation dient. Diese Zeichenkette – Docstring genannt – wird direkt unterhalb des Funktionskopfs notiert: Listing 3.9: Funktion mit Docstring 01 02 03 04 05 06 07
def zusammenfassen(*begriffe): """Funktion verbindet beliebig viele Zeichenketten. Der Parameter begriffe nimmt die übergebenen Positionsargumente auf und kann als Tupel ausgewertet werden. Es wird eine Zeichenkette zurückgegeben."""
99
3 Die Struktur von Python
08 09 10 11 12
verbindung = '' for item in begriffe: verbindung += item return verbindung
Üblicherweise schreibt man den Docstring in dreifache Anführungszeichen, weil man meistens mehrere Zeilen Text zur Erläuterung benötigt. Die erste Zeile sollte kurz beschreiben, was die Funktion leistet, dann kann weiterer Text – getrennt durch eine leere Zeile – folgen, der nähere Erläuterungen zur Funktion gibt. Hier ist es sinnvoll, anzugeben, welchen Datentyp die Parameter haben müssen und welcher Datentyp von der Funktion zurückgegeben wird. Arbeitet man in einem Team, sind auch die Angabe des Autors und das letzte Änderungsdatum von Vorteil. Dieser Docstring kann von der help-Funktion ausgelesen werden: >>> help(zusammenfassen) Help on function zusammenfassen in module __main__: zusammenfassen(*begriffe) Funktion verbindet beliebig viele Zeichenketten. Der Parameter begriffe nimmt die übergebenen Positionsargumente auf und kann als Tupel ausgewertet werden. Es wird eine Zeichenkette zurückgegeben.
Wenn man das Skript aufgerufen hat, kann die help-Funktion den Docstring auslesen und zurückgeben. Dazu wird der Funktionsname an die help-Funktion übergeben. Auf diese Weise kann man auch die Docstrings der Standardfunktionen auslesen und erhält so wichtige Informationen zu ihrer Verwendung: >>> help(len) Help on built-in function len in module __builtin__: len(...) len(object) -> integer Return the number of items of a sequence or mapping.
Eine weitere Möglichkeit, den Docstring einzusehen, besteht darin, das Attribut __doc__ abzurufen, das jede Funktion besitzt: >>> print zusammenfassen.__doc__ Funktion verbindet beliebig viele Zeichenketten. Der Parameter begriffe nimmt die übergebenen Positionsargumente auf und kann als Tupel ausgewertet werden. Es wird eine Zeichenkette zurückgegeben.
100
3.6 Funktionen
Es empfiehlt sich, diesen Aufruf mit der print-Anweisung durchzuführen, weil hierbei die Formatierung des Docstrings erhalten bleibt. Ohne print-Anweisung erhält man eine Zeichenkette, in der die Escapesequenzen sichtbar sind, was für die Lesbarkeit weniger vorteilhaft ist. Bei Funktionen, die viele Parameter besitzen, kann es für die Dokumentation von Vorteil sein, jeden Parameter einzeln mit einem Kommentar zu versehen. Dies kann nach der Regel für Kommentare in Klammerausdrücken wie in Abschnitt 2.1.3 beschrieben erfolgen. Auch hier gilt wieder, dass insbesondere bei der Arbeit in Teams die Verwendung von aussagekräftigen Kommentaren einer effektiven Programmentwicklung förderlich ist.
101
4
Klassen entwickeln
4.1
Die Klasse als Bauplan
Wenn man von objektorientierter Programmierung spricht, dann meint man in der Regel die Entwicklung von Klassen, von denen im Programmablauf Instanzen gebildet werden. Diese Instanzen werden Objekte genannt. Die Klasse fungiert dabei als eine Art Bauplan, der die Eigenschaften (Attribute) und Funktionsweisen (Methoden) des Objekts beschreibt. Alle Objekte, mit denen bislang gearbeitet wurde, waren von Klassen abgeleitet, die von den Python-Entwicklern vorgegeben sind. Hier soll es jetzt darum gehen, eigene Klassen zu entwickeln. Da man es bei der Entwicklung von Software vielfach mit Objekten und Prozessen aus der Realwelt zu tun hat, muss man versuchen, die realen Objekte in das objektorientierte Entwicklungsmodell zu übertragen. Als Beispiel für einen solchen Übertrag soll ein (fiktives) Modell aus der Welt des Zugverkehrs dienen; hier das Objekt Zug. Sehr vereinfacht kann man einen Zug mit folgender Tabelle beschreiben: Zug Eigenschaften (Methoden)
Geschwindigkeit freie Sitzplätze
»Verhalten« (Attribute)
Fahren Halten Einsteigen Aussteigen
Zustände
in Fahrt gestoppt leer besetzt
Tabelle 4.1: Einfaches Modell eines Zugs Natürlich gibt es eine Vielzahl weiterer Eigenschaften und Verhaltensweisen eines Zugs, aber hier soll es zunächst nur um das Prinzip der Modellierung einer Klasse
4 Klassen entwickeln
gehen. Dafür reicht es, das »Verhalten« Fahren oder Ein- und Ausstieg zu beschreiben und die Eigenschaften Geschwindigkeit und Sitzplätze aufzulisten. Aus einem solchen Modell kann man nun ein Klassendiagramm entwickeln, das bereits einige wichtige Elemente der tatsächlichen Klasse enthält. Die Form des Klassendiagramms ist durch die Unified Modeling Language (UML) festgelegt. Diese Sprache dient der Modellierung von Softwareprogrammen. Sie stellt eine Reihe von Bezeichnern und grafischen Elementen zur Verfügung, mit denen Softwaresysteme theoretisch und konzeptionell entwickelt werden können. Tipp Eine Einführung in UML findet sich unter der Adresse http://www. highscore.de/uml/index.html. Wenn man eine einheitliche Modellierungssprache hat, liegt der Schluss nahe, dass man aus den Modellen auch automatisch Code generieren kann. Neben dem kommerziellen Programm Enterprise Architect (http://www.sparxsystems.de/), das seit der Version 6.0 Python-Code generieren kann, versucht sich auch das freie Dia2Code (http://dia2code.sourceforge.net/index.html) darin, Python zu generieren. Für einen professionellen Einsatz ist dieses Tool aber nicht zu empfehlen. Man sollte allerdings nicht davon ausgehen, dass hier automatisch Programme geschrieben werden. Es bleibt weiterhin viel Handarbeit zu leisten. Die Beschreibung für den Zug wird in folgendes Diagramm umgesetzt: Train speed <____> seats changeSpeed() getIn() getOut()
Tabelle 4.2: Klassendiagramm des Zugs Im oberen Teil des Diagramms wird der Name der Klasse notiert. Die Eigenschaften Geschwindigkeit und Sitzplätze finden sich in den Attributen speed bzw. seats im mittleren Feld wieder. Dabei wird speed als Klassenattribut festgelegt. Die Verhaltensweisen Fahren und Halten werden mit der Methode changeSpeed() abgedeckt. Das Einbzw. Aussteigen wird durch getIn() bzw. getOut() repräsentiert. Das Klassendiagramm beschreibt also den strukturellen Aufbau der Klasse. Werden von der Klasse Instanzen erzeugt, können diese unterschiedliche Werte für die Attribute haben. So
104
4.2 Eine Klasse definieren
können zwei Züge unterschiedliche Mengen an Sitzen haben oder mit unterschiedlicher Geschwindigkeit fahren. Die von der Klasse erzeugten Objekte unterscheiden sich in ihren Attributen und führen ein voneinander unabhängiges »Leben«.
4.2
Eine Klasse definieren
Eine Klassendefinition wird mit dem Schlüsselwort class eingeleitet. Wie bei einer Funktion wird mit ihm der Kopf der Klasse eingeleitet. Es folgen dann der Klassenname sowie ein Klammernpaar und den Abschluss bildet wieder ein Doppelpunkt. Alle folgenden Zeilen sind dann mindestens eine Tabstufe eingerückt. Eine einfache Klassendefinition sieht folgendermaßen aus: class ErsteKlasse(): pass
Diese Klasse definiert natürlich letztendlich nichts, zeigt aber einen korrekten Aufbau. Bei der Namensvergabe für Klassen gelten die gleichen Regeln wie für Funktionen oder Variablen (siehe Abschnitt 2.1.4). Es hat sich durchgesetzt, dass Klassennamen mit einem Großbuchstaben beginnen. So ist es möglich, im Programmcode Klassen- von Funktionsaufrufen zu unterscheiden. Eine »richtige« Klassendefinition besteht aus folgenden Elementen: Einem Kopf, eingeleitet mit dem Schlüsselwort class, gefolgt von einem Klassen-
namen und gegebenenfalls Vererbungsangaben. Die Definition von Klassenattributen. Dies sind Attribute, deren Anfangswert für
alle Klasseninstanzen gleich ist. Die Definition der Konstruktormethode. Ihr Name ist auf __init__(self) festgelegt.
Die Konstruktormethode wird bei der Instanzierung der Klasse, also der Bildung von Objekten aus dieser Klasse, aufgerufen. Der Parameter self ist eine Selbstreferenz. Er bezeichnet das instanzierte Objekt. Jede Methode einer Klasse benötigt diesen Parameter und dieser wird immer als Erstes in der Parametersequenz angegeben. Die Definition weiterer Methoden, die die Funktionalität der Klasse beschreiben.
Die Klasse Train würde demnach entsprechend des Klassendiagramms im vorigen Abschnitt folgendermaßen notiert: Listing 4.1: Grundaufbau der Klasse Train 01 class Train(objekt): 02 speed = 0 03 04 def __init__(self,seats): 05 self.seats = seats 06
Entsprechend der obigen Auflistung beginnt die Klassendefinition mit der Vergabe des Namens Train und in Klammern der Angabe einer anderen Klasse, von der Train erbt (zur Vererbung siehe Abschnitt 4.8). Jede Klasse sollte object als Basisklasse haben. Sie stellt einige grundlegende Funktionen für Klassen zur Verfügung. Es ist möglich, auf diese Vererbung zu verzichten, was aber in der Version 3.0 von Python geändert werden soll. Dann wird es zwingend notwendig sein, object als Basisklasse für eigene Klassen anzugeben. Es folgt die Definition der Variablen speed als Klassenattribut (Zeile 02). Es wird mit dem Anfangswert 0 belegt, da jeder Zug zunächst stehen muss, damit ein Zugführer einsteigen kann, um überhaupt das Gefährt in Gang zu setzen. In Zeile 04 und 05 findet sich die Konstruktormethode __init__(), die neben self noch einen weiteren Parameter besitzt. An diesen Parameter wird bei der Instanzierung ein Wert übergeben, der dann den Anfangswert des Attributs seats bestimmt. Hier sieht man deutlich den Unterschied zum Klassenattribut. Letzteres bekommt seinen Wert fest (hartkodiert) zugewiesen, während das Instanzattribut seinen Wert erst bei der Instanzierung erhält (siehe Abschnitt 4.4 zu weiteren Besonderheiten von Klassenattributen). Ab Zeile 07 beginnen dann die Definitionen der Methoden changeSpeed(), getIn(), getOut(). Methoden sind Funktionen eines Objekts. Deshalb wird ihre Definition auch mit dem Schlüsselwort def eingeleitet. Der einzige Unterschied zur »normalen« Funktionsdefinition ist, dass die Methode in jedem Fall den Parameter self besitzt (siehe Abschnitt 4.5). Noch besitzen die Methoden der Klasse Train keine Funktionalität.
4.3
Eine Klasse aufrufen: Objekte bilden
Normalerweise strukturiert man ein Programm so, dass Klassen in Modulen organisiert werden, die dann zunächst importiert werden (siehe Abschnitt 4.9). Nach dem Import ist dann die Klasse aufrufbar. Zur Demonstration reicht es aber aus, das Skript aus dem vorigen Abschnitt in der Python IDLE auszuführen und die Klasse im interaktiven Modus zu testen. Der Aufruf der Klasse sieht dann folgendermaßen aus: >>> zug1 = Train(270)
106
4.3 Eine Klasse aufrufen: Objekte bilden
Dieser Aufruf erzeugt ein Objekt, das in der Variablen zug1 gespeichert wird: >>> zug1 <__main__.Train object at 0x01052F50>
Lässt man sich den Inhalt der Variablen ausgeben, erhält man eine Objektreferenz. Von dieser Klasse können nun beliebig viele Objekte erzeugt werden: >>> zug2 = Train(430) >>> zug2 <__main__.Train object at 0x01052D90>
An der Referenz-Id erkennt man, dass es sich bei zug2 um eine eigene Instanz der Klasse Train handelt. Hinweis Theoretisch bildet die Größe des Arbeitsspeichers eine Grenze für die Anzahl der erstellten Objekte. Man dürfte aber in der Praxis kaum einmal an diese Grenze stoßen. Python besitzt eine automatische Speicherbereinigung, die den Speicher von nicht mehr verwendeten Objekten freigibt. Man spricht hier auch von Garbage-Collection. Erst wenn mehrere tausend Objekte gleichzeitig aktiv sind, könnte es zu einem Speicherproblem kommen. Die beiden erzeugten Objekte unterscheiden sich auch in den Werten des Attributs seats: >>> zug1.seats 270 >>> zug2.seats 430
Diese Werte wurden beim Erstellen der Objekte als Parameter an die Klasse übergeben und von der Konstruktormethode __init__() verarbeitet. Der Zugriff auf die Objektattribute erfolgt in der gewohnten Weise: Der Attributname wird mit einem Punkt getrennt hinter dem Objektnamen notiert. Auch der Zugriff auf Klassenattribute erfolgt auf diese Weise: >>> zug1.speed 0
Man kann so auch direkt den Wert eines Attributs verändern: >>> zug1.speed = 100 >>> zug1.speed 100
107
4 Klassen entwickeln
Ändert man die Attribute eines Objekts, ändert man dessen Zustand. Dies entspricht dem Verhalten von Objekten in der realen Welt. Ein Zug mit 300 Passagieren an Bord, der 300 km/h unterwegs ist, befindet sich in einem anderen Zustand als ein stehender, leerer Zug. Objektattribute bilden diese Zustände ab. Schaut man sich einmal das Objekt zug1 mit der Funktion dir() an, stellt man fest, dass alle definierten Attribute und Methoden verzeichnet sind: >>> dir(zug1) ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', 'changeSpeed', 'getIn', 'getOut', 'seats', 'speed']
Die übrigen Namen, die in der Ausgabeliste enthalten sind, stammen von der Basisklasse object.
4.4
Ein genauer Blick auf Klassenattribute
Klassenattribute existieren unabhängig von den Instanzen, die von der Klasse gebildet werden. Es ist möglich, sie über die Klasse selbst aufzurufen. Das aber geht nur dann, wenn die Klasse selbst auch ein Objekt ist. Und ist das der Fall, müsste ihr Aufruf eine Objektrepräsentanz zurückgeben: >>> Train
Wie man sieht, erhält man eine solche Repräsentanz in der Python-Shell angezeigt. Klassen sind in der Tat Objekte, die von sogenannten Metaklassen instanziert werden. Es sollte nun also der Aufruf des Klassenattributs speed möglich sein: >>> Train.speed 0
In der Tat wird ihr Wert ausgegeben. Im vorigen Abschnitt wurde aber schon das Attribut speed für das Objekt zug1 auf den Wert 100 gesetzt. Und tatsächlich erhält man mit >>> zug1.speed 100
diesen Wert nach wie vor angezeigt. Ändert man auch das Attribut für zug2 und fragt dann die Attribute nochmals ab, ergibt sich folgendes Bild: >>> zug2.speed = 150 >>> zug2.speed 150 >>> zug1.speed
108
4.4 Ein genauer Blick auf Klassenattribute
100 >>> Train.speed 0
Die Objekte zug1 und zug2 haben ihre eigenen Werte für das Attribut, während das Klassenattribut davon unberührt bleibt. Tatsächlich sind die beiden Attribute von zug1 und zug2 Instanzvariablen, die nach der Instanzierung gebildet wurden. Ruft man für eines der Objekte das Attribut speed auf, wird zunächst in der Instanz nach diesem Namen gesucht. Ist er dort nicht vorhanden, wird in der Klasse gesucht. Ist er dort auch nicht vorhanden, wird ein Fehler ausgelöst. Im jetzigen Zustand der beiden Instanzen ist es also nicht mehr ohne Weiteres möglich, die Klassenvariable über die Instanz aufzurufen. Schaut man sich aber den Namensraum der Instanz an (voriger Abschnitt), dann findet man dort das Attribut __class__. Mit ihm ist es möglich, wieder auf das Klassenattribut zuzugreifen: >>> zug1.__class__.speed 0
Dieser Zugriff ist natürlich auch in der Klassendefinition verwendbar, so dass man dafür sorgen kann, dass Instanzen jederzeit auf Klassenattribute zugreifen können. Da Klassenattribute den Instanzen übergeordnet sind, kann man sich z. B. einen Zähler vorstellen, der die Anzahl der gebildeten Instanzen festhält. Die Klasse muss dafür nur geringfügig erweitert werden: Listing 4.2: Klasse mit Instanzzähler 01 class Train(object): 02 speed = 0 03 inst_count = 0 04 05 def __init__(self,seats): 06 self.seats = seats 07 self.__class__.inst_count += 1 08 09 def changeSpeed(self): 10 pass 11 12 def getIn(self): 13 pass 14 15 def getOut(self): 16 pass 17 18 def showInstCount(self): 19 return self.__class__.inst_count
109
4 Klassen entwickeln
In Zeile 03 wird ein weiteres Klassenattribut definiert. Es erhält den Anfangswert 0. In der Konstruktormethode wird es um 1 erhöht (Zeile 07), also mit jeder Instanzierung der Klasse. Schließlich wird noch eine weitere Methode hinzugefügt, die den aktuellen Wert des Attributs zurückgibt. Bildet man nun Instanzen von dieser Klasse, kann man über jede den Wert dieses Klassenattributs abrufen: >>> >>> 1 >>> >>> 2 >>> >>> 3 >>> 3
Mit jeder Instanzierung wird der Wert von inst_count um 1 erhöht. Unabhängig davon, über welche Instanz man die Methode showInstCount() aufruft, erhält man den Wert des übergeordneten Attributs.
4.5
Methoden
Für Methoden gelten die gleichen Regeln wie für Funktionen (siehe Abschnitt 1.5). Insbesondere auch die Regeln für Parameter finden Anwendung. Zusätzlich gilt die Regel, dass die Parametersequenz mit dem Parameter self beginnen muss. An diesen Parameter wird quasi automatisch die Objektinstanz übergeben, so dass man im Funktionskörper darauf zugreifen kann. Dies kann man durch einen einfachen Test sichtbar machen: 01 class Test(): 02 def getInstance(self): 03 return self
Diese Testklasse besitzt nur eine Methode, die self zurückgibt. Wenn man jetzt eine Instanz von der Klasse bildet, kann man Folgendes testen: >>> a = Test() >>> a <__main__.Test instance at 0x02681080> >>> a.getInstance() <__main__.Test instance at 0x02681080>
Ruft man nach Bildung der Instanz diese auf, erhält man die Objektreferenz. Die gleiche Referenz erhält man aber auch, wenn man die Methode aufruft, die self ausgibt.
110
4.5 Methoden
Es ist übrigens nicht zwingend notwendig, den ersten Parameter mit self zu benennen. Es ist vielmehr so, das die Instanz immer an den ersten Parameter übergeben wird. Es würde also auch Folgendes funktionieren: 01 class Test(): 02 def getInstance(ich): 03 return ich
Es ist aber allgemein verbindlich, in der Python-Programmierung hier den Namen self zu verwenden. Diese Konvention sorgt mit dafür, dass die Quellen auch für Dritte lesbar sind. Der Aufruf einer Methode kann nur über die Instanz erfolgen. Das bedeutet, dass zunächst eine Instanz gebildet werden muss, bevor überhaupt ein Aufruf erfolgen kann. Ein Aufruf über die Klasse funktioniert nicht. (Es ist aber möglich, sogenannte Klassenmethoden zu definieren. Siehe dazu Abschnitt 4.5.1.) Um das Verwenden von Methoden an Beispielen zu erläutern, sollen zunächst die drei Methoden der Train-Klasse mit Leben gefüllt werden: Listing 4.3: Die Klasse Train mit »richtigen« Methoden 01 class Train(object): 02 speed = 0 03 04 def __init__(self,seats): 05 # Platzanzahl festlegen 06 self.seats = seats 07 08 def changeSpeed(self, speed, action): 09 # Abfangen von negativen Werten 10 if speed < 0: 11 print 'Nur positive Zahlen eingeben' 12 return 13 # Geschwindigkeit verringern 14 if action == 'bremsen': # 15 act = -1 # Multiplikator bestimmen 16 if self.speed <= speed: 17 print 'Zug f„hrt bereits langsamer' 18 return 19 # Geschwindigkeit erhöhen 20 elif action == 'beschleunigen': 21 act = 1 # Multiplikator bestimmen 22 else: 23 print "Erbitte klare Anweisung" 24 return 25 # Geschwindigkeitsänderung durchführen 26 while self.speed != speed: 27 self.speed += 10*act
print self.speed def getIn(self,people): # Prüfen, ob Zug steht if self.speed == 0: self.seats -= people # Platzanzahl verringern else: print '''Einsteigen nicht möglich. Zug f„hrt mit %s km/h.''' %(self.speed) def getOut(self,people): # Prüfen, ob Zug steht if self.speed == 0: self.seats += people # Platzanzahl erhöhen else: print '''Aussteigen nicht möglich. Zug f„hrt mit %s km/h.''' %(self.speed)
Die Methoden sind schnell erklärt: changeSpeed() erhält zwei zusätzliche Parameter, mit denen die Endgeschwindigkeit und die Aktion (Bremsen oder Beschleunigen) geregelt werden. Es wird geprüft, ob ein negativer Wert eingegeben wurde (Zeile 10) und dann, je nach Aktion, ein Multiplikator (act) bestimmt wurde (Zeile 14–21). Wenn eine nicht eindeutige Aktion eingegeben wird, wird eine entsprechende Meldung ausgegeben. Schließlich wird dann die Geschwindigkeit entsprechend der Eingaben geändert und per print-Anweisung ausgegeben (Zeile 26–28). Die Änderung erfolgt in Zehnerschritten, die Variable act bestimmt, ob runter- oder raufgezählt wird. Die beiden Methoden getIn() und getOut() prüfen zunächst, ob der Zug steht (speed = 0), und lassen nur dann eine Änderung der Sitzplatzzahl zu. Diese Zahl wird den Methoden über den Parameter people übergeben. Bildet man nun eine Instanz der Klasse, kann man mit den Methoden arbeiten: >>> zug = Train(400)
Es steigen 50 Personen ein: >>> zug.getIn(50)
Die Methode getIn() wird also durch einen Punkt getrennt nach dem Objektnamen notiert. Nach dem Aufruf ist die Platzanzahl geringer geworden: >>> zug.seats 350
112
4.5 Methoden
Nun kann der Zug sich in Bewegung setzen: >>> zug.changeSpeed(120,'beschleunigen') 10 20 30 40 50 60 70 80 90 100 110 120
Der Zug fährt mit 120 km/h. Ein- oder Aussteigen ist nun nicht möglich: >>> zug.getOut(2) Aussteigen nicht möglich. Zug fährt mit 120 km/h.
Es greift die eingebaute Sicherung, die die entsprechende Meldung ausgibt. Auf diese Weise kann mit den Methoden das »Verhalten« eines Zugs simuliert werden. Zugegebenermaßen ist diese Klasse viel zu primitiv angelegt, um ein so komplexes System wie einen Zug wirklich genau abzubilden.
4.5.1
Statische Methoden und Klassenmethoden
Die Methoden, die bislang behandelt wurden, sind Instanzmethoden. Das bedeutet, sie sind nur dann aufrufbar, wenn eine Instanz von der Klasse gebildet wurde. Klassen aber sind eben auch Objekte, die wie in Abschnitt 4.4 gezeigt, Attribute haben können. Es stellt sich also die Frage, ob Klassen auch Methoden haben können. Und in der Tat, mit den Konzepten statische Methode und Klassenmethode stehen sogar zwei Typen von Methoden zur Verfügung, die über die Klasse aufgerufen werden können. Zunächst ein Beispiel für eine Klasse mit statischen Methoden, die einige Kreisberechnungen durchführt: Listing 4.4: Klasse mit statischen Methoden für Kreisberechnungen 01 class Kreis(): 02 @staticmethod 03 def flaeche(radius): 04 pi = 3.14159265 05 return radius**2*pi 06 07 @staticmethod 08 def umfang(radius):
113
4 Klassen entwickeln
09 10 11 12 13 14 15
pi = 3.14159265 return radius*2*pi @staticmethod
def zylinder(radius,hoehe): pi = 3.14159265 return radius**2*pi*hoehe
Die Klasse Kreis enthält drei Methoden, die den Radius und den Umfang eines Kreises sowie das Volumen eines Zylinders berechnen. Die Deklaration zur statischen Methode erfolgt über einen sogenannten Dekorator. Dieser wird direkt vor der Methodendefinition notiert und mit dem @-Zeichen eingeleitet. Diese schnelle Möglichkeit der Deklaration steht seit der Version 2.4 von Python zur Verfügung. Bei der Einführung von statischen und Klassenmethoden wurden diese über eine Standardfunktion zu solchen deklariert: class Kreis(): def flaeche(radius): pi = 3.14159265 return radius**2*pi kreisflaeche = staticmethod(flaeche)
Man musste also einen weiteren Namen vergeben, der die statische Methode repräsentierte. Mit den Dekoratoren ist dies nun nicht mehr notwendig. Statische Methoden enthalten nicht den Parameter self. Sie können also nicht auf die Instanz der Klasse zugreifen und damit auch nicht auf Instanzattribute. Aber auch der Zugriff auf Klassenattribute bleibt ihnen verwehrt, da sie ebenfalls keine Referenz auf die Klasse besitzen. Und schließlich können statische Methoden nicht auf andere statische oder auf Instanzmethoden zugreifen. Instanzmethoden dagegen können auf statische Methoden wie auf jede andere Methode zugreifen. Statische Methoden eignen sich gut, um – wie im obigen Beispiel – inhaltlich, thematisch oder funktional ähnliche Methoden unter dem Dach einer Klasse zusammenzufassen und sie schnell und ohne Instanzierung verfügbar zu machen. Der Aufruf einer solchen Methode kann schnell und direkt über die Klasse erfolgen: >>> Kreis.flaeche(20) 1256.63706
Durch die Deklaration als statische Methode ist aber der Zugriff über die Instanz nicht ausgeschlossen. Dies macht deshalb Sinn, weil man natürlich auch Klassen mit statischen und Instanzmethoden haben kann: >>> circle = Kreis() >>> circle.umfang(10) 62.831853000000002
114
4.6 Zugriffe steuern
Mit Klassenmethoden kann man das obige Beispiel noch etwas effektiver aufbauen: Listing 4.5: Klasse für Kreisberechnungen mit Klassenmethoden 01 class Kreis(): 02 pi = 3.14159265 03 @classmethod 04 def flaeche(cls,radius): 05 return radius**2*cls.pi 06 07 @classmethod 08 def umfang(cls,radius): 09 return radius*2*cls.pi 10 11 @classmethod 12 def zylinder(cls,radius,hoehe): 13 return cls.flaeche(radius)*hoehe
Klassenmethoden werden mit dem Dekorator @classmethod als solche deklariert. Auch hier existiert noch die veraltete Möglichkeit, dies mit der Standardfunktion classmethod() durchzuführen. An den ersten Parameter einer Klassenmethode wird die Klasse selbst übergeben. Dies funktioniert ähnlich wie beim Parameter self der Instanzmethoden. Es ist auch hier jeder beliebige Name möglich, es hat sich aber der Name cls allgemein durchgesetzt. Über diese Referenz auf die Klasse selbst wird es nun möglich, sowohl auf Klassenattribute als auch auf andere Klassenmethoden zuzugreifen. Im Beispiel kann dadurch die Kreiszahl Pi als Klassenattribut definiert werden und in den Methoden entsprechend verwendet werden (Zeile 05 und 09). Die Methode zylinder() kann zudem die Methode flaeche() für eine Teilberechnung verwenden (Zeile 13). Die Aufrufe dieser Methoden erfolgen wie bei den statischen Methoden: >>> Kreis.zylinder(10,120) 37699.111799999999
Und auch der Aufruf über eine Instanz ist mit Klassenmethoden möglich: >>> circle = Kreis() >>> circle.umfang(12) 75.398223600000009
4.6
Zugriffe steuern
Bislang wurde auf Attribute nur direkt zugegriffen. Insbesondere der zuweisende Zugriff ist in der direkten Form problematisch. In der Klasse Train ist es z. B. nicht erwünscht, für das Attribut speed einen negativen Wert einzugeben. Dies ist aber jederzeit möglich in der Form:
115
4 Klassen entwickeln
zug1.speed = -100
Diesen direkten Zugriff versucht man in der objektorientierten Programmierung nach Möglichkeit zu vermeiden, indem man Methoden für die Zuweisung, aber auch für das Auslesen von Werten definiert. In diesen Methoden ist es dann möglich, Fehleingaben abzufangen. In der Klasse Train existiert mit changeSpeed() bereits eine Methode, die solche Fehleingaben abfängt. Um nun zu verhindern, dass ein direkter Zugriff auf das Attribut speed stattfindet, kann dieses nach außen hin abgeschottet werden. Dies erreicht man durch das Voranstellen von zwei Unterstrichen (_) vor den Attributsnamen. Aus speed wird dann __speed. Ein solches Attribut wird auch als privat bezeichnet. So geändert sieht die Klasse Train nun folgendermaßen aus: Listing 4.6: Änderung des Attributs speed zu privat 01 class Train(object): 02 __speed = 0 03 04 def __init__(self,seats): 05 # Platzanzahl festlegen 06 self.seats = seats 07 08 def changeSpeed(self, speed, action): 09 # Abfangen von negativen Werten 10 if speed < 0: 11 print 'Nur positive Zahlen eingeben' 12 return 13 # Geschwindigkeit verringern 14 if action == 'bremsen': # 15 act = -1 # Multiplikator bestimmen 16 if self.__speed <= speed: 17 print 'Zug f„hrt bereits langsamer' 18 return 19 # Geschwindigkeit erhöhen 20 elif action == 'beschleunigen': 21 act = 1 # Multiplikator bestimmen 22 else: 23 print "Erbitte klare Anweisung" 24 return 25 # Geschwindigkeitsänderung durchführen 26 while self.__speed != speed: 27 self.__speed += 10*act 28 print self.__speed 29 30 def getIn(self,people): 31 # Prüfen, ob Zug steht 32 if self.__speed == 0:
116
4.6 Zugriffe steuern
33 34 35 36 37 38 39 40 41 42 43 44
self.seats -= people # Platzanzahl verringern else: print '''Einsteigen nicht möglich. Zug f„hrt mit %s km/h.''' %(self.__speed) def getOut(self,people): # Prüfen, ob Zug steht if self.__speed == 0: self.seats += people # Platzanzahl erhöhen else: print '''Aussteigen nicht möglich. Zug f„hrt mit %s km/h.''' %(self.__speed)
Versucht man nun, direkt auf __speed zuzugreifen, gelingt dies nicht mehr. Es wird stattdessen ein Fehler ausgegeben: >>> zug1 = Train(150) >>> zug1.__speed Traceback (most recent call last): File "", line 1, in <module> zug1.__speed AttributeError: 'Train' object has no attribute '__speed'
Private Attribute sind also nur innerhalb der Klasse bzw. einer Instanz der Klasse abrufbar. Dies kann man daran ablesen, dass die Methode changeSpeed() nach wie vor funktioniert: >>> zug1.changeSpeed(80,'beschleunigen') 10 20 30 40 50 60 70 80
Mit dem Privatschalten des Attributs __speed hat man aber auch die Möglichkeit verloren, die aktuelle Geschwindigkeit des Zugs abzufragen. Aus diesem Grunde benötigt die Klasse jetzt eine Methode für diese Aufgabe: Listing 4.7: Methode zum Abrufen der Geschwindigkeit def getSpeed(self): return self.__speed
Es ist allgemein üblich, den Namen von einer Methode, die den Wert eines Attributs zurückgibt, mit get beginnen zu lassen. Die Namen einfacher Methoden, die den Wert eines Attributs ändern, beginnt man mit set, also z. B. setSpeed(). Es scheint aufwän-
117
4 Klassen entwickeln
dig zu sein, nur für die Rückgabe eines Werts eine eigene Methode zu definieren. Aber im Sinne »sicherer« Attribute kommt man bei der Definition von Klassen nicht darum herum. Nicht nur Attribute können privat geschaltet werden, auch Methoden kann man vor dem Zugriff von außen abschotten. Das scheint auf den ersten Blick etwas unlogisch zu sein, will man doch mit Methoden das Verhalten von Objekten steuern. Jedoch wenn man die Klasse Train um eine Methode erweitert, die das Aus- und Einsteigen in einem Vorgang regelt, kann man die Methoden getIn() und getOut() privat schalten. Die Erweiterung macht Sinn, da das Ein- und Aussteigen in der Realität teilweise parallel stattfindet. Die Erweiterung sieht dann folgendermaßen aus: Listing 4.8: Private Methoden und ihre Verwendung 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
… def __getIn(self,people): if self.__speed == 0: self.seats -= people else: print '''Einsteigen nicht möglich. Zug fährt mit %s km/h.''' %(self.speed) def __getOut(self,people): if self.__speed == 0: self.seats += people else: print '''Aussteigen nicht möglich. Zug fährt mit %s km/h.''' %(self.speed) def changePeople(self,p_in=0,p_out=0): if p_in: self.__getIn(p_in) if p_out: self.__getOut(p_out) print 'Aktuell freie Plätze: %s' %(self.seats)
Die neue Methode changePeople() kann Zahlen für ein- und aussteigende Personen entgegennehmen. Diese reicht sie an die nunmehr privaten Methoden __getIn() bzw. __getOut() weiter. Es besteht nun keine Notwendigkeit mehr, dass diese beiden Methoden von außen aufrufbar sind. Versucht man es trotzdem, erhält man auch hier eine Fehlermeldung: >>> zug = Train(400) >>> zug.__getIn() Traceback (most recent call last): File "", line 1, in <module> zug.__getIn() AttributeError: 'Train' object has no attribute '__getIn'
118
4.7 Polymorphie – Operatoren überladen
Dagegen funktioniert der Aufruf »von innen« durch changePeople() einwandfrei: >>> zug.changePeople(p_in=45) Aktuell freie Plätze: 355
In Python unterscheidet man zwischen starker und schwacher Privatheit. Alle bisherigen Beispiele haben starke private Namen gezeigt. Schwache private Namen beginnen mit nur einem Unterstrich, z. B. _testname. Schwach private Attribute sind von außen erreichbar, sie werden aber bei einem Import nicht in den aktuellen Namensraum importiert. Das bedeutet, dass man nur dann zugreifen kann, wenn man Kenntnis vom Namen hat. Letztlich aber kann man auch auf stark private Namen zugreifen, wenn man sich einer speziellen Syntax bedient: >>> zug._Train__speed 0
Man notiert vor dem Namen des Attributs den Namen der Klasse, dem ein Unterstrich vorangestellt wird. Beginnt ein Name in einer Klasse mit zwei Unterstrichen, wird dieser intern nämlich mit einem Namen nach folgendem Muster verwaltet: _Klassenname__name
Ruft man die Funktion dir() für die Klasse auf, kann man dies auch sehen: >>> dir(Train) ['_Train__getIn', '_Train__getOut', '_Train__speed', '__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', 'changePeople', 'changeSpeed', 'getSpeed']
4.7
Polymorphie – Operatoren überladen
Der Begriff Polymorphie stammt aus dem Griechischen und bedeutet »Vielgestaltigkeit«. In der Programmierung bezeichnet man damit das Konzept, einem Wert oder einem Namen verschiedene Datentypen zuzuordnen. Eine Form der Polymorphie ist das Überladen von Funktionen oder Operatoren (deren Verhalten von Funktionen gesteuert wird). In Python ist unter anderem der +-Operator überladen, weil er auf Zeichenketten und auf Zahlen anwendbar ist. Während er bei Zeichenketten eine Konkatenation durchführt, also zwei oder mehrere Zeichenketten zu einer verbindet, führt er bei Zahlen eine Addition durch. In Abschnitt 3.1 wurde bereits gezeigt, dass hinter der +-Operator die Methode __add__() steht. Die gilt für ein Zahlenobjekt genauso wie für ein Zeichenkettenobjekt: >>> b='text' >>> b.__add__('aufgabe') 'textaufgabe'
119
4 Klassen entwickeln
Das bedeutet, in der Klasse, die das Verhalten von Zeichenketten festlegt, ist eine Methode mit dem Namen __add__() definiert. Und in der Tat – schaut man sich den Namensraum eines Zeichenkettenobjekts an, findet man: >>> dir(b) ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', ...
Neben vielen anderen Namen, die hier nicht alle angezeigt sind, findet sich gleich an erster Stelle __add__. Diese Methode wird aufgerufen, wenn man zwei Zeichenketten mit dem +-Operator verbindet. Eigene Klassen kann man ebenfalls mit einer gleichnamigen Methode ausstatten, um Instanzen der Klasse mit dem +-Operator verbinden zu können. Wie die Objekte dann verbunden werden, wird durch die Programmierung im Methodenkörper geregelt. Eine erneute Erweiterung der Klasse Train soll die Anwendung verdeutlichen. Im Laufe seiner Fahrt steigen in einen Zug etliche Personen ein. Für Auswertungszwecke ist es sinnvoll, wenn die eingestiegenen Personen addiert werden. Man kann so die Gesamtzahl der Fahrgäste ermitteln. Fahren nun aber viele Züge, so will man auch deren Fahrgastzahlen hinzuaddieren. Dies ist ein Fall für die Methode __add__(): Listing 4.9: Anwendung der Methode __add__() 01 class Train(object): 02 __speed = 0 03 04 def __init__(self,seats): 05 self.__seats = seats 06 self.__passengers = 0 … 50 def getSeats(self): 51 """ Gibt die Anzahl der freien Plätze zurück """ 52 return self.__seats 53 54 def getPassengers(self): 55 """ Gibt die Gesamtzahl der Passagiere zurück """ 56 return self.__passengers 57 58 def changePeople(self,p_in=0,p_out=0): 59 """ Regelt das Ein- und Austeigen """ 60 if p_in: 61 self.__getIn(p_in) 62 self.__passengers += p_in # Addition aller Einsteigenden 63 if p_out: 64 self.__getOut(p_out) 65 print 'Aktuell freie Plätze: %s' %(self.__seats) 66 67 def __add__(self,zug):
Es wird zunächst ein Attribut benötigt, das die Passagierzahlen mitzählt. Dies ist das Attribut __passengers, das gleich privat angelegt wird (nebenbei ist auch __seats privat geschaltet worden, was aber für dieses Beispiel keine Bedeutung hat). Es wird dann eine Methode benötigt, die die Zahl ausgeben kann. Diese ist mit getPassengers() implementiert (Zeile 46 und 47). Das eigentliche Zählen der Passagiere erfolgt in der Methode changePeople(), dort wo auch __getIn() aufgerufen wird (Zeile 52). Die Methode __add__() erhält einen Parameter, dem die Instanz des zu addierenden Zugs übergeben wird (Zeile 57). Von dieser Instanz wird die Methode getPassengers() aufgerufen und der Rückgabewert zu __passengers addiert (Zeile 58). Schließlich wird eine Ausgabe des Ergebnisses gemacht. Ein Testablauf sieht z. B. so aus: >>> rb1 = Train(250) >>> rb1.changePeople(p_in= 25) Aktuell freie Plätze: 225 >>> rb2 = Train(230) >>> rb2.changePeople(p_in= 43) Aktuell freie Plätze: 187 >>> rb1 + rb2 Gesamtpasssgierzahl: 68
Es wurde also mit der Methode add_() eine weitere Überladung des +-Operators für die Klasse Train geschaffen. Es gibt eine Reihe weiterer Methodennamen, die für Überladungen reserviert sind. Man spricht hier auch von magischen Methoden. Es gibt in Python eine ganze Reihe vorbelegter Namen, die für magische Methoden gedacht sind. Sie beginnen alle mit einem doppelten Unterstrich. Die folgende Tabelle listet einige von ihnen auf: Magische Methode
Operator
__add__(self, instance)
+ (Plusoperator)
__sub__()
– (Minusoperator)
__mul__(self, instance)
* (Multiplikationsoperator)
__div__(self, instance)
/ (Divisionsoperator)
__mod__(self, instance)
% (Modulooperator)
__pow__(self, instance)
** (Potenzierungsoperator)
__eq__(self, instance)
== (ist gleich)
__ne__((self,instance)
!= (ist nicht gleich)
__ge__(self, instance)
>= (größer oder gleich)
Tabelle 4.3: Reservierte Methodennamen für magische Methoden
121
4 Klassen entwickeln
Magische Methode
Operator
__gt__(self, instance)
> (größer als)
__le__(self, instance)
<= (kleiner oder gleich)
__lt__(self, instance)
< (kleiner als)
__iadd__(self, instance)
+= (inkrement)
__isub__(self, instance)
–= (dekrement)
__imul__(self, instance)
*= (inkrementelle Multiplikation)
__idiv__(self, instance)
/= (dekrementelle Division)
__ipow__( a, b)
**= (inkrementelles Potenzieren)
__iconcat__( self, instance)
+= (Verbindung von Sequenzen)
__irepeat__( self, integer)
*= (Multiplikation einer Sequenz)
__neg__( self, instance)
– (Negationsoperator)
__setitem__(self, key, value)
obj[key] = value (Zuweisung für Container-Klassen)
__delitem__(self, key)
del obj[key] (Löschen in Container-Klassen)
Tabelle 4.3: Reservierte Methodennamen für magische Methoden (Forts.)
Tipp Weitere Operatoren und ihre Methodennamen finden sich in der Python Library Reference in Abschnitt 6.7.1 unter der Adresse http:// docs.python.org/lib/module-operator.html.
4.8
Vererbung
Besonders effizient wird die objektorientierte Programmierung durch das Konzept der Vererbung. Ein Klasse kann eine (oder mehrere) Klasse als Basis haben und »erbt« von dieser Basisklasse alle Methoden und Attribute. Sie wird dadurch zur Unterklasse der Basisklasse. Zusätzlich kann eine Unterklasse eigene Methoden und Attribute besitzen, womit sie sich gegenüber der Basisklasse spezialisiert. Wenn eine Klasse von einer Klasse aus einem anderen Modul erben soll, muss dieses zunächst importiert werden (siehe auch Abschnitt 4.9). Dies ist nicht notwendig, wenn Basis- und Unterklasse in einem Modul notiert sind. Im folgenden Beispiel, in dem eine Unterklasse von Train entwickelt werden soll, wird auf diese Weise verfahren. Diese Unterklasse definiert eine Zugfahrt:
122
4.8 Vererbung
Listing 4.10: Unterklasse zur Definition einer Zugfahrt 61 class Ride(Train): 62 def __init__(self,seats,line): 63 Train.__init__(self,seats) 64 self.__line = line 65 self.__stops = 0 66 67 def stationStop(self): 68 self.changeSpeed(0,'bremsen') 69 if self.__stops < len(self.__line): 70 self.__stops += 1 71 print "Jetzt Halt in %s" %(self.__line[self.__stops]) 72 73 def __str__(self): 74 return "Zug von %s nach %s." %(self.__line[0], self.__line[-1])
Wenn die Basis- und die Unterklasse gleiche Namen (für Attribute oder Methoden) besitzen, dann überschreiben die Namen der Unterklasse die Namen der Basisklasse. Im Beispiel besitzen beide Klassen die Methode __init__(). Deshalb muss die Konstruktormethode der Basisklasse in der Konstruktormethode der Unterklasse explizit aufgerufen werden. Der Parameter seats wird an sie weitergereicht (Zeile 63). Der Parameter line bestimmt die Strecke, die der Zug fährt. Es muss ein Tupel oder eine Liste mit Städtenamen übergeben werden. Normalerweise würde man eine Prüfung des Datentyps vornehmen, damit die Instanz korrekt arbeiten kann. Der Einfachheit halber wurde hier darauf verzichtet. Das Attribut __stopps ist eine Variable, die die Anzahl der Bahnhofstopps zählen soll. Die Methode stationStop() ruft die Methode changeSpeed() aus der Klasse Train auf, um den Zug zum Halten zu bringen (Zeile 68). Die Methoden der Basisklasse lassen sich aufgrund der Vererbung wie eigene Methoden aufrufen. Gleiches gilt auch für Attribute. Schließlich wird eine Ausgabe des aktuellen Halts gemacht. Die Methode __str__() überlädt die Funktion print. Mit ihr wird es möglich, eine Ausgabe über den print-Befehl zu erhalten. Man kann jetzt eine Instanz der Klasse Ride bilden, die sich wie eine Instanz der Klasse Train verhält, darüber hinaus aber noch weitere Merkmale hat. >>> fahrt = Ride(400,['Hamburg','Berlin','Leipzig','München']) >>> dir(fahrt) ['_Ride__line', '_Ride__stops', '_Train__getIn', '_Train__getOut', '_Train__passengers', '_Train__seats', '_Train__speed', '__add__', '__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', 'changePeople', 'changeSpeed', 'getPassengers', 'getSeats', 'getSpeed', 'stationStop']
123
4 Klassen entwickeln
Die Funktion dir() zeigt, dass alle Namen der Klasse Train im Namensraum des Objekts enthalten sind. Man kann jetzt Personen einsteigen lassen und den Zug in Fahrt bringen: >>> fahrt.changePeople(p_in=77) Aktuell freie Plätze: 323 >>> fahrt.changeSpeed(100,'beschleunigen') 10 ... 100
Mit der neuen Methode stationStop() kann dann der Halt in der ersten Stadt simuliert werden: >>> fahrt.stationStop() 90 ... 0 Jetzt Halt in Berlin
Schließlich kann man mit der print-Anweisung Informationen über die Fahrt erhalten: >>> print fahrt Zug von Hamburg nach München
Diese Ausgabe erzeugt die magische Methode __str__() der Klasse Ride.
4.9
Module
Hat man wie im Beispiel dieses Kapitels seine Klassen in einer Datei gespeichert, können andere Skripte sie importieren. Eine solche Datei wird als Modul bezeichnet. Bei größeren Projekten teilt man in der Regel seinen Code auf mehrere Dateien auf, um mit überschaubaren Dateien arbeiten zu können. In einer einzelnen Datei werden dann meistens Konstanten, Funktionen und Klassen zusammengefasst, die strukturell zusammengehören. Python selbst besteht aus einer Vielzahl von Modulen und es können weitere Module hinzugefügt werden, um den Funktionsumfang zu erweitern. Damit ein Modul importiert werden kann, benötigt es einen Namen. Dies ist der Name der Datei ohne die Endung .py. Wenn also die beiden Klassen des Beispiels in der Datei Railroad.py abgespeichert sind, dann ist der Modulname Railroad. Der Import erfolgt mit der Anweisung import, gefolgt vom Modulnamen: >>> import Railroad
124
4.9 Module
Damit wird der Name des Moduls dem aktuellen Namensraum hinzugefügt. Will man nun auf die Klassen zugreifen, muss man den Modulnamen voranstellen: >>> fahrt = Railroad.Ride(300,['Köln', 'Bonn', 'Koblenz'])
Will man direkt auf die Klassen zugreifen oder nur bestimmte Klassen importieren, kann man folgende Form des Imports verwenden: >>> from Railroad import Ride
Jetzt wird die Klasse Ride dem aktuellen Namensraum hinzugefügt. Die Instanzierung von dieser Klasse kann nun direkt erfolgen: >>> fahrt = Ride(300,['Köln', 'Bonn', 'Koblenz'])
Es ist dabei nicht notwendig, Klassen, von denen Ride erbt, also die Klasse Train, ebenfalls zu importieren. Über die Vererbung gehen alle Namen der Basisklasse auch in den aktuellen Namensraum ein. Wenn man alle Namen eines Moduls importieren will, kann man folgende Form des Imports wählen: >>> from Railroad import *
Es ist aber insbesondere bei Modulen mit vielen Klassen, Methoden und Attributen Vorsicht geboten, da Namenskollisionen entstehen können. Existiert im aktuellen Namensraum bereits ein Name, der auch im importierten Modul vorkommt, so wird er überschrieben und verweist nun auf das Objekt des Moduls.
4.9.1
Suchpfade
Damit eine Python-Datei als Modul importiert werden kann, muss sie auch auffindbar sein. Befindet es sich im gleichen Verzeichnis wie das Skript, das es importieren soll, ist die Auffindbarkeit gewährleistet. Darüber hinaus sucht Python aber noch in weiteren Verzeichnissen nach einem Modul, das importiert werden soll. Welche Verzeichnisse das sind, kann man auf folgende Weise ermitteln: >>> import sys >>> sys.path ['C:\\Python25\\Lib\\idlelib', 'C:\\Zope270\\bin', 'C:\\WINNT\\system32\\python25.zip', 'C:\\Python25\\DLLs', 'C:\\Python25\\lib', 'C:\\Python25\\lib\\plat-win', 'C:\\Python25\\lib\\lib-tk', 'C:\\Python25', 'C:\\Python25\\lib\\site-packages', 'C:\\Python25\\lib\\site-packages\\PIL', 'C:\\Python25\\lib\\site-packages\\Pythonwin']
125
4 Klassen entwickeln
Man erhält mit sys.path eine Liste aller Verzeichnisse, in denen der Interpreter nach einem Modul sucht. Das Verzeichnis [Python-Hauptverzeichnis]/lib/site-packages ist das Standardverzeichnis für Zusatzmodule. Man kann dieser Liste auch Pfade hinzufügen und auf diese Weise Python veranlassen, auch in den Verzeichnissen zu suchen, in denen man seine Module abgelegt hat: >>> sys.path.append(r'c:\eisenbahn\module')
Erweitern lässt sich die Liste auch über die Umgebungsvariable PYTHONPATH. Je nach Betriebssystem wird die Umgebungsvariable anders gesetzt und verändert. Es ist aber keine gute Idee, sich auf einen Pfad im PYTHONPATH zu verlassen, wenn man seine Skripte Dritten zur Verfügung stellen will, da man nicht weiß, wie PYTHONPATH auf anderen Systemen gesetzt ist. Wenn man ein Modul importiert, wird es von Python kompiliert. Man findet dann im Verzeichnis des Moduls eine weitere Datei mit dem gleichen Namen, aber der Endung .pyc. Wenn der Interpreter im Optimierungsmodus läuft, ist die Endung .pyo. Beide Dateien enthalten einen kompilierten Programmtext in plattformunabhängigem Bytecode. Bei einem erneuten Import wird nun die kompilierte Datei verwendet, was das Laden des Moduls beschleunigt.
126
5
Arbeiten mit Zeichenketten
Der Datentyp, mit dem man bei der Programmierung vermutlich am häufigsten umgeht, ist die Zeichenkette. Viele Daten werden als Zeichenketten abgespeichert und wieder gelesen. Wenn man z. B. HTML-Seiten betrachtet, dann besitzen sie mit dem HTML-Code zwar eine spezielle Auszeichnung, sie sind aber letztlich Textdokumente. Ähnlich verhält es sich mit XML-Dokumenten. Mit ihnen lassen sich komplexe Datenstrukturen abbilden, das einzelne XML-Dokument bleibt aber ein Textdokument. Die Beispiele lassen sich beliebig fortsetzen: Konfigurationsdateien von Programmen und Betriebssystemen sind Textdateien und natürlich auch Python-Skripte, solange sie noch nicht kompiliert wurden. Man sieht, dass man im alltäglichen (Computer-)Leben über die klassische Textverarbeitung hinaus eigentlich ständig mit Textdateien zu tun hat. Deshalb sollte eine gute Programmiersprache entsprechende Werkzeuge für den Umgang mit Texten (Zeichenketten) haben. Neben den Methoden für nicht änderbare Sequenzen (siehe Abschnitt 3.2.3) besitzt das String-Objekt noch siebenunddreißig eigene Methoden, von denen die wichtigsten in diesem Kapitel beleuchtet werden. Hinweis Übrigens ist der Satz an Methoden für Unicode-Strings und RawStrings nahezu identisch. Man kann also alle Methoden in der gleichen Weise für alle Arten von Zeichenketten verwenden. Tatsächlich besitzt das Unicode-Objekt die zwei zusätzlichen Methoden isdecimal() und isnumeric().
Allen Methoden ist eines gemeinsam: Sie geben eine Kopie der Zeichenkette zurück, auf die sie angewendet wurden. Da Zeichenketten zu den nicht änderbaren Datentypen gehören, wird also eine neue Zeichenkette durch die Anwendung einer Methode erstellt. Die ursprüngliche Zeichenkette bleibt unverändert: >>> zk = "Monty Python's Flying Circus" >>> zk.lower() "monty python's flying circus" >>> zk "Monty Python's Flying Circus"
5 Arbeiten mit Zeichenketten
Wie man sieht, gibt die Methode lower() die Zeichenkette in Kleinbuchstaben zurück. Die in zk abgelegte Zeichenkette bleibt aber unverändert.
5.1
Die Methoden des String-Objekts
5.1.1
Groß- und Kleinschreibung
Drei Methoden existieren, um die Groß- und Kleinschreibung zu manipulieren: lower() – erzeugt eine Kopie mit lauter Kleinbuchstaben upper() – erzeugt eine Kopie in Großbuchstaben capitalize() – erzeugt eine Kopie, die mit einem Großbuchstaben beginnt und in
der Folge nur noch Kleinbuchstaben enthält swapcase() – erzeugt eine Kopie, in der Klein- in Großbuchstaben und umgekehrt
geändert wurden Und so sieht ihre Anwendung aus: >>> zk = "Monty Python's Flying Circus" >>> zk.lower() "monty python's flying circus" >>> zk.upper() "MONTY PYTHON'S FLYING CIRCUS" >>> zk.capitalize() "Monty python's flying circus" >>> zk.swapcase() "mONTY pYTHON'S fLYING cIRCUS"
5.1.2
Zeichenketten überprüfen
Man kann überprüfen, welche Beschaffenheit eine Zeichenkette hat. Dafür stehen folgende Methoden zur Verfügung: islower() – gibt True zurück, wenn die Zeichenkette nur Kleinbuchstaben enthält.
Leer- und Satzzeichen werden nicht berücksichtigt. isupper() – gibt True zurück, wenn die Zeichenkette nur Großbuchstaben enthält.
Leer- und Satzzeichen werden nicht berücksichtigt. isalpha() – gibt True zurück, wenn die Zeichenkette nur Buchstaben enthält. isdigit() – gibt True zurück, wenn die Zeichenkette nur Ziffern enthält. isalnum() – gibt True zurück, wenn die Zeichenkette nur Buchstaben und Ziffern
enthält. isspace() – gibt True zurück, wenn die Zeichenkette nur Leerzeichen enthält.
128
5.1 Die Methoden des String-Objekts
startswith(string) – gibt True zurück, wenn die Zeichenkette mit den für string
übergebenen Zeichen beginnt endswith(string) – gibt True zurück, wenn die Zeichenkette mit den für string über-
gebenen Zeichen endet. Einige Anwendungsbeispiele: >>> zk = "heute geht es besser" >>> zk.isdigit() False >>> zk.islower() True >>> zk.isalnum() False >>> zk.startswith('heu') True
5.1.3
Suchen und Ersetzen
Eine der häufigsten Arbeiten, die man mit Zeichenketten durchführen muss, ist das Suchen und Ersetzen von Teilstrings. Dies kann mit folgenden Methoden erledigt werden: count(string,[start,end]) – gibt die Anzahl der Vorkommnisse des Teilstrings string zurück. Mit den optionalen Parametern start und ende, für die Indexzahlen
übergeben werden können, kann die Suche auf einen Teil der Zeichenkette begrenzt werden. find(string,[start,end]) – gibt den Index des ersten Treffers von string zurück. Mit den optionalen Parametern start und ende, für die Indexzahlen übergeben werden
können, kann die Suche auf einen Teil der Zeichenkette begrenzt werden. Ist der Teilstring nicht vorhanden, wird der Wert –1 zurückgegeben. index(string,[start,end]) – funktioniert wie find(), löst aber einen Fehler aus, wenn
der Teilstring nicht gefunden wird. rfind(string,[start,end] – gibt den Index des letzten Treffers von string zurück.
Die Zeichenkette wird vom Ende her durchsucht. Mit den optionalen Parametern start und ende, für die Indexzahlen übergeben werden können, kann die Suche auf einen Teil der Zeichenkette begrenzt werden. rindex(string,[start,end]) – funktioniert wie rfind(), löst aber einen Fehler aus,
wenn der Teilstring nicht gefunden wird. replace(alt, neu, [anzahl]) – alle Vorkommnisse des Teilstrings alt werden durch neu ersetzt. Mit dem optionalen Parameter anzahl kann bestimmt werden, dass nur die ersten durch anzahl bestimmten Vorkommnisse ersetzt werden. translate(tabelle,[delete]) – ersetzt alle Zeichen anhand einer Ersetzungstabelle, die als erster Parameter eingegeben wird. Mit dem optionalen Parameter delete
können Zeichen angegeben werden, die gelöscht werden.
129
5 Arbeiten mit Zeichenketten
Einige Anwendungsbeispiele: >>> zk = "Das Leben des Brian" >>> zk.count('e') 3 >>> zk.find('e') 5 >>> zk.rfind('e') 11 >>> zk.replace('e','u') 'Das Lubun dus Brian' >>> tab = '' >>> for i in range(256): tab += chr((i+7)%256) >>> zk.translate(tab) "Khz'Slilu'klz'Iyphu"
Für die Verwendung der Methode translate() wurde zuvor eine Ersetzungstabelle erstellt, bei der die Anordnung der Buchstaben um sieben Positionen verschoben ist.
5.1.4
Bereinigen und Auffüllen
Insbesondere bei Benutzereingaben kann es notwendig werden, Zeichenketten am Anfang und Ende von Leerzeichen oder Tabulatoren (Whitespace) zu befreien, um eine einwandfreie Weiterverarbeitung zu gewährleisten. Auch dafür stehen entsprechende Methoden zur Verfügung. Andere Methoden wiederum können Zeichenketten für die geordnete Ausgabe auf der Konsole mit Leerzeichen füllen: lstrip([chars]) – entfernt ohne Angabe eines Parameters Whitespace am Anfang der Zeichenkette. Werden für chars Zeichen angegeben, werden diese entfernt. rstrip([chars]) – entfernt ohne Angabe eines Parameters Whitespace am Ende der Zeichenkette. Werden für chars Zeichen angegeben, werden diese entfernt. strip([chars]) – entfernt ohne Angabe eines Parameters Whitespace am Anfang und Ende der Zeichenkette. Werden für chars Zeichen angegeben, werden diese
entfernt. ljust(width) – die Zeichenkette wird am Ende mit Leerzeichen aufgefüllt, bis der Gesamtstring width Zeichen enthält. rjust(width) – die Zeichenkette wird am Anfang mit Leerzeichen aufgefüllt, bis der Gesamtstring width Zeichen enthält. center(width) – die Zeichenkette wird am Anfang und Ende mit Leerzeichen aufgefüllt, bis der Gesamtstring width Zeichen enthält.
130
5.1 Die Methoden des String-Objekts
zfill(width) – die Zeichenkette wird am Anfang mit Nullen (0) aufgefüllt, bis der Gesamtstring width Zeichen enthält. Ist width kleiner als die Länge der Zeichenket-
te, wird diese unverändert zurückgegeben. Die Methode eignet sich, um Zahlwerte mit führender Null auszugeben. expandtabs(size) – ersetzt Tabulatoren in der Zeichenkette mit der für size angege-
benen Anzahl an Leerzeichen. Ohne Angabe des Parameters wird eine Tabulatorweite von acht Leerzeichen verwendet. Einige Beispiele: >>> zk = ' Platz lassen >>> zk.lstrip() 'Platz lassen '
Man kann Zeichenketten zerlegen und sie in Listen überführen, um sie so weiterzuverarbeiten. Dafür und für das Zusammensetzen existieren einige Methoden: split([sep],[max]) – zerlegt ohne Angabe von Parametern eine Zeichenkette anhand von Leerzeichen. Die Bestandteile werden als Liste zurückgegeben. Für sep
kann ein anderes Zeichen angegeben werden, anhand dessen die Zeichenkette aufgeteilt wird. Mit max gibt man an, wie viele einzelne Elemente abgeteilt werden sollen. Der Rest der Zeichenkette ist dann ein einzelnes Element in der zurückgelieferten Liste. rsplit([sep],[max]) – funktioniert wie split(). Erst bei Angabe eines Werts für max
ergibt sich ein anderes Ergebnis. Die Abteilung der Elemente beginnt am Ende der Zeichenkette.
131
5 Arbeiten mit Zeichenketten
splitlines() – zerteilt eine Zeichenkette in Zeilen – also anhand des Zeilenum-
bruchs – und gibt diese als Liste zurück. partition(sep) – liefert ein Tupel mit drei Elementen zurück. Das erste Element ist der Teilstring links vom Separator, das zweite Element ist der Separator selbst und das dritte Element der Teilstring rechts von Separator. Wird der Separator nicht gefunden, sind die beiden letzten Elemente im Tupel leere Zeichenketten. rpartition(sep) – funktioniert wie partition(), die Suche nach dem Separator wird aber vom Ende der Zeichenkette begonnen. Wird dieser nicht gefunden, sind die beiden ersten Elemente im zurückgegebenen Tupel leere Zeichenketten. join(sequenz) – fügt die übergebene Sequenz, die Zeichenketten enthalten muss, zu
einer neuen Zeichenkette zusammen. Die Zeichenkette, für die die Methode aufgerufen wird, ist das Trennzeichen zwischen den Elementen. Einige Beispiele: >>> zk = "Die Ritter der Kokosnuss" >>> zk.split() ['Die', 'Ritter', 'der', 'Kokosnuss'] >>> zk.rsplit(' ',1) ['Die Ritter der', 'Kokosnuss'] >>> zk.split(' ',1) ['Die', 'Ritter der Kokosnuss'] >>> l = ['Das', 'Leben', 'des', 'Brian'] >>> '---'.join(l) 'Das---Leben---des---Brian' >>> zk = "Heute.schon.gelebt" >>> zk.partition('.') ('Heute', '.', 'schon.gelebt') >>> zk.rpartition('.') ('Heute.schon', '.', 'gelebt')
5.1.6
Kodieren und Decodieren
Zeichen bestehen nicht aus einzelnen Buchstaben, sondern unterliegen einer Codierung. Die Standardcodierung in Python ist ASCII, die aber nur Buchstaben aus dem angelsächsischen Sprachraum kodiert. Sie enthält 128 Zeichen, von denen 95 ausdruckbar sind. Deutsche Umlaute sind nicht enthalten. Die Codierung Latin-1 (ISO 8859-1) enthält 256 Zeichen, von denen die Zeichen 0 bis 127 mit dem ASCII-Code übereinstimmen. Die weiteren Plätze sind mit Buchstaben aus den westeuropäischen Alphabeten besetzt. Die Codierung UTF-8 (Unicode Transport Format) ist in der Lage, ungefähr eine Million Zeichen darzustellen. Die ersten 128 Zeichen entsprechen
132
5.2 Variable Zeichenketten
auch hier dem ASCII-Code. Ausgangspunkt für die Codierung ist immer ein Unicode-String. Mit zwei Methoden können Zeichensätze in Python in eine andere Codierung überführt und wieder dekodiert werden: encode([encoding]) – kodiert einen Unicode-String. Für den Parameter encode wird die Zielcodierung angegeben. Ohne Parameter ist die Zielcodierung 'ascii'. Für
Zeichenketten mit deutschen Umlauten ist diese Codierung nicht geeignet. decode([encoding]) – wandelt eine Zeichenkette in einen Unicode-String um. Für den Parameter encoding muss die Quellcodierung der Zeichenkette eingeben werden. Ohne Parameter wird ascii als Quellcodierung verwendet. Für Zeichenketten
mit deutschen Umlauten sollte latin-1 verwendet werden. Beispiele: >>> zk = 'über alle Maßen' >>> uni = zk.decode('latin-1') >>> uni u'\xfcber alle Ma\xdfen' >>> utf = uni.encode('utf-8') >>> print utf über alle Maßen >>> print utf.decode('utf-8') über alle Maßen
Es kann sein, dass eine Codierung in der Konsole nicht richtig darstellbar ist. Das ist abhängig vom der Codierung der Betriebssystems. Deshalb muss man unter Umständen selbst für die richtige Codierung bei der Ausgabe sorgen. Wenn einmal alle Systeme auf UTF-8 umgestellt sind, hat man dieses Problem nicht mehr. Dies ist bei Windows seit der Einführung von Vista der Fall. Bis Windows XP wurde eine eigene Codierung (Windows 1252) verwendet. Unter MacOS und Linux (Debian, Ubuntu, SuSE, RedHat u. a.) wird schon länger systemweit UTF-8 zur Zeichencodierung verwendet.
5.2
Variable Zeichenketten
Wie schafft man es auf eine einfache und effiziente Weise, Variablenwerte in einer Zeichenkette zu platzieren? Wenn man z. B. in einem Dictionary Informationen zu einem Produkt gespeichert hat und diese in einer gut lesbaren Weise ausgeben möchte, kann man das z. B. so machen: >>> art = {'artnr': 2364,'name': 'Kaugummi', 'preis': 0.50} >>> print 'ArtNr.: '+str(art['artnr'])+', Name: ' +art['name']+', Preis: '+str(art['preis']) Artikelnummer: 2364, Name: Kaugummi, Preis: 0.5
133
5 Arbeiten mit Zeichenketten
Die Ausgabe ist zwar gut lesbar, der Quelltext aber ist es nicht. Zudem ist diese Art, die Variablen per +-Operator einzufügen, in der Verarbeitung ineffizient. Es steht aber ein anderer Operator zur Verfügung, der auch als String-Modulo bezeichnet wird, mit dem sich wesentlich eleganter die Variablen in den Text einfügen lassen. Hier das obige Beispiel mit diesem Operator: >>> print 'ArtNr.: %s, Name: %s, Preis: %s' %(art['artnr'], art['name'], art['preis'])
ArtNr.: 2364, Name: Kaugummi, Preis: 0.5
In die Zeichenkette werden Platzhalter eingefügt, die mit dem Zeichen % beginnen und eine Formatierungsanweisung beinhalten. Im Beispiel ist dies der Buchstabe s. Nach dem Ende der Zeichen wird dann ein Tupel notiert, dem ebenfalls ein % vorangestellt wird. Im Tupel werden die Werte notiert, die bei der Ausgabe für die Platzhalter eingefügt werden. Dabei spielt die Reihenfolge der Werte eine entscheidende Rolle. Sie muss der Reihenfolge der Platzhalter im Text entsprechen, damit eine korrekte Ausgabe erfolgen kann. Man hat durch diese Art der Variableneinfügung nicht nur einen lesbareren Quelltext, sondern spart sich auch Typenumwandlungen. Diese werden automatisch vorgenommen. Die Platzhalter in der Zeichenkette bestimmen zudem, wie der Variablenwert eingefügt wird. Schaut man sich einmal die Ausgabe des Preises an, so fehlt dort die zweite Stelle hinter dem Komma. Zwar ist sie im Dictionary eingetragen, da es sich aber um eine Fließkommazahl handelt, ist die 0 an der zweiten Stelle überflüssig. Bei der Umwandlung wird sie entfernt. Das kann man verhindern, indem man eine andere Formatanweisung einfügt: >>> print ' ArtNr.: %s, Name: %s, Preis: %.2f' %(art['artnr'], art['name'], art['preis']) Artikelnummer: 2364, Name: Kaugummi, Preis: 0.50
Tipp Alle Formatierungsanweisungen finden sich im Abschnitt 3.6.1 der Python Library Reference unter der Adresse http://docs.python.org/ lib/typesseq-strings.html.
134
5.3 Das Modul string
Eine Fehlerquelle bei dieser Form der Variableneinbettung ist die Reihenfolge der Werte im Tupel. Aber es gibt eine noch elegantere Form der Verwendung von Platzhaltern, die diesen Fehler ausschließt. Man übergibt an die Zeichenkette ein Dictionary und notiert zu den Platzhaltern den Schlüssel, über den man auf den Wert im Dictionary zugreift: >>> print ' ArtNr.: %(artnr)s, Name: %(name)s, Preis: %(preis).2f' %art
Jetzt spielt die Reihenfolge der Platzhalter keine Rolle mehr. Über die Schlüssel sind die Werte eindeutig zuzuordnen. Die Schlüssel werden in Klammern nach dem %-Zeichen notiert und – dies ist zu beachten – ohne Anführungszeichen.
5.3
Das Modul string
In älteren Python-Versionen war die Umsetzung der Datentypen als Objekte noch nicht komplett vollzogen. Aus diesem Grunde existiert noch das Modul string, das die gleichen Methoden enthält wie das String-Objekt (siehe Abschnitt 5.1). Vermutlich wird es in der Version Python 3.0 ganz verschwunden sein. Das Modul enthält aber einige Konstanten, die in der täglichen Arbeit mitunter ganz nützlich sein können. Die folgende Tabelle listet diese auf und zeigt, welche Zeichenketten sie enthalten. Konstante
Tabelle 5.1: Konstanten des string-Moduls (Forts.) Das folgende Skript, das ein Passwort generiert, zeigt eine Anwendung einiger Konstanten: Listing 5.1: Ein Passwort generieren 01 from string import ascii_lowercase, ascii_uppercase, digits 02 import random 03 def createPW(length): 04 # einen Pool an verfügbaren Zeichen bilden 05 pool = ascii_lowercase + digits + ascii_uppercase 06 pw = '' 07 # Zählschleife für Bildung des Passwortes 08 for i in range(length): 09 # Ein Zufallszahl aus den Indexzahlen des Pools bilden 10 ind = random.choice(range(len(pool)-1)) 11 #Zeichen an dieser Position dem Passwort hinzufügen 12 pw += pool[ind] 13 return pw
Mithilfe dreier String-Konstanten wird ein Pool an Zeichen gebildet (Zeile 05). Daraus werden Zeichen an zufälliger Position für das Passwort entnommen (Zeile 10 und 11). Ruft man die Funktion auf, erhält man eine zufällig zusammengesetzte Zeichenkette: >>> createPW(8) 'IfJiTarR'
5.4
Das Modul textwrap
Für die Ausgabe von Text in vorgegebenen Breiten, z. B. auf Internetseiten, ist das Modul textwrap interessant. Mit seinen Methoden kann man einen Text nach einer bestimmten Anzahl von Zeichen umbrechen. Dabei werden einzelne Worte, wenn sie die vorgegebene Grenze überschreiten, nicht getrennt, sondern in die folgende Zeile übernommen.
136
5.4 Das Modul textwrap
Ein Beispiel: >>> import textwrap >>> text = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.' >>> print textwrap.fill(text, width=40) Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Die Methode fill() liefert den eingegebenen Text mit Umbrüchen versehen zurück. Über den Parameter width wird die Textbreite bestimmt. Mit der Methode wrap() erhält man als Ergebnis eine Liste, in der jedes Element eine Zeile im neu umgebrochenen Text darstellt. >>> print textwrap.wrap(text, width=40) ['Lorem ipsum dolor sit amet, consetetur', 'sadipscing elitr, sed diam nonumy eirmod', 'tempor invidunt ut labore et dolore', 'magna aliquyam erat, sed diam voluptua.', 'At vero eos et accusam et justo duo', 'dolores et ea rebum. Stet clita kasd', 'gubergren, no sea takimata sanctus est', 'Lorem ipsum dolor sit amet.']
Die Zeichen für den Zeilenumbruch liefert dies Methode nicht mit. Diese müssen bei der Weiterverarbeitung der Liste eingefügt werden. Über einige weitere Parameter kann das Ausgabeergebnis der beiden Methoden gesteuert werden: expand_tabs – Tabulatoren werden durch Leerzeichen ersetzt. Die Standardeinstellung ist True. replace_whitespace – alle Whitespace-Zeichen werden durch ein einzelnes Leerzeichen ersetzt. Die Standardeinstellung ist True. initial_indent – es kann eine Zeichenkette angegeben werden, die am Anfang der
ersten Zeile hinzugefügt wird. subsequent_indent – es kann eine Zeichenkette angegeben werden, die am Anfang
jeder Zeile – mit Ausnahme der ersten – hinzugefügt wird. fix_sentence_endings – versucht, Satzenden zu erkennen und zu gewährleisten,
dass zwischen zwei Sätzen immer zwei Leerzeichen gesetzt werden. Diese Funktion ist noch nicht ausgereift und höchstens für englische Texte sinnvoll anzuwenden. Die Standardeinstellung ist False.
137
5 Arbeiten mit Zeichenketten
break_long_words – Worte, die länger sind als der Wert, der für den Parameter width angegeben wurde, werden umgebrochen. Die Standardeinstellung ist True. Wenn der Parameter auf False gesetzt wird, können einzelne Zeilen länger werden, als bei width angegeben.
5.5
Reguläre Ausdrücke
Mit regulären Ausdrücken sucht man nach Vorkommnissen bestimmter Muster in einer Zeichenkette. So ist das Muster eb in weben, heben oder geben enthalten. Mit Sonderzeichen und besonderen Zeichensequenzen definiert man komplexe Muster. So ist der Punkt (.) ein Platzhalter für genau ein beliebiges Zeichen. Ein regulärer Ausdruck wie W.ld passt demnach auf Wald oder Wild. Mit Zeichenklassen werden bestimmte Mengen von Zeichen festgelegt. Der reguläre Ausdruck W\Dld definiert das vorige Beispiel etwas exakter, da \D für eine Zeichenklasse steht, die keine Ziffern enthält. Das Muster passt also weiterhin auf Wald, nicht aber auf W3ld. Zeichenklassen können auch selbst definiert werden, indem man mögliche Zeichen in eckige Klammern setzt. Mit dem Muster W[aeiou]ld grenzt man die passenden Zeichenketten weiter ein. Es passt nun nur noch auf Wald, Weld, Wild, Wold und Wuld. Sogenannte Quantifizierer regeln, wie häufig ein Ausdruck vorkommen kann. Mit einem Stern (*) kann ein Ausdruck beliebig oft vorkommen (auch null Mal). So passt das Muster W[aeiou]*ld auf Wald, aber auch auf Waaaald, Wld oder Waaeeiioouuld. Eine exakte Quantifizierung kann man mit einer Zahl in geschweiften Klammern vornehmen. Das Muster \d{5} passt auf jede fünfstellige Zahl. Die folgende Tabelle listet die wichtigsten Zeichen für reguläre Ausdrücke auf. Zeichen
Beschreibung
Metazeichen ^
Beginn der Eingabe
$
Ende der Eingabe
\
Escape-Zeichen
( )
Begrenzung für eine Gruppe
[ ]
Begrenzung für eine Zeichenklasse
|
Alternative (oder)
Zeichenklassen \d
Ziffern
\D
Alle Zeichen außer Ziffern
Tabelle 5.2: Zeichen für reguläre Ausdrücke
138
5.5 Reguläre Ausdrücke
Zeichen
Beschreibung
Metazeichen ^
Beginn der Eingabe
$
Ende der Eingabe
\s
Whitespace (Leerzeichen, Tab, Return)
\S
Alle Zeichen außer Whitespace
\w
Buchstabe, Ziffer, oder Unterstrich
\W
Alle Zeichen außer Buchstabe , Ziffer oder Unterstrich
.
Ein beliebiges Zeichen außer Return
Quantifizierer ?
Einmal oder keinmal
*
Beliebig oft (auch keinmal)
+
Beliebig oft (aber mindestens einmal)
{n}
Genau n Mal
{n,}
Mindestens n Mal
{,n}
Höchstens n Mal
{n1,n2}
Zwischen n1 und n2 Mal
Tabelle 5.2: Zeichen für reguläre Ausdrücke (Forts.)
5.5.1
Das Modul re
Für die Arbeit mit regulären Ausdrücken steht das Modul re zur Verfügung, das zur Python-Grundinstallation gehört. Die Arbeit mit regulären Ausdrücken beginnt fast immer damit, ein Suchmuster (Pattern) an die Methode compile() zu übergeben. Man erhält ein Objekt zurück, das verschiedene Methoden für die eigentliche Suche enthält. Anhand eines Beispiels soll die grundsätzliche Vorgehensweise gezeigt werden: Listing 5.2: Whitespace identifizieren 01 from re import * 02 def findWS(): 03 text = raw_input('Eingabe: ') 04 print text 05 # Bildung des Suchmusters 06 p = compile('\s+\w+|\w+\s+') 07 # Entfernung von Whitespace, falls vorhanden 08 if p.search(text): 09 text = text.strip() 10 print text
139
5 Arbeiten mit Zeichenketten
Das Skript identifiziert Whitespace am Anfang und am Ende einer Zeichenkette und entfernt diesen. Das kann für die Weiterverarbeitung von Benutzereingaben sinnvoll sein. Es beginnt mit dem Import aller Namen aus dem Modul re. Damit wird es möglich, Funktionsaufrufe direkt zu notieren. Die Benutzereingabe wird mit der Funktion raw_input() ermöglicht (Zeile 03). Es folgt eine print-Anweisung, die in Zeile 10 noch mal wiederholt wird. Damit kann man in der Ausgabe sehen, wie die Zeichenkette vor und nach der Suche aussieht. Mit der Methode compile() wird ein Suchmusterobjekt erstellt. Als Parameter wird das Suchmuster übergeben (Zeile 06). Mit der Methode search() wird nun die Suche durchgeführt. Als Parameter erhält diese Methode den zu durchsuchenden Text (Zeile 08). Wenn es Treffer im Text gibt, am Anfang oder Ende also Whitespace vorhanden ist, wird dieser entfernt (Zeile 09). Führt man die Funktion aus, gibt es folgendes Ergebnis: >>> findWS() Eingabe: hallo hallo
hallo
In der ersten Ausgabe vor der Anwendung des Suchmusters ist der Whitespace noch vorhanden. Nach der Anwendung ist dieser entfernt. Macht man den Test und gibt einen Zeichenkette ohne Whitespace ein, erhält man nur eine Ausgabe: >>> findWS() Eingabe: hallo hallo
Die print-Anweisung im if-Block wurde nun nicht ausgeführt, weil die Methode search() None zurückgibt, wenn es keinen Treffer gibt. Das Suchmusterobjekt hat noch einige andere Methoden, die im Folgenden erläutert werden: findall(text) – gibt eine Liste mit allen Vorkommnissen entsprechend des Such-
musters zurück. match(text,[start, ende]) – gibt ein Trefferobjekt (Match-Objekt) zurück, wenn das Suchmuster am Anfang des Textes vorkommt. Mit den Parametern start und ende
kann ein Suchbereich definiert werden. search(text[,start, ende]) – gibt ein Trefferobjekt zurück, wenn das Suchmuster überhaupt im Text vorkommt. Mit den Parametern start und ende kann ein Such-
bereich definiert werden. split(text[,max]) – gibt eine Liste von Teilstrings zurück. Die Trennzeichen werden durch das Suchmuster definiert. Der Parameter max bestimmt gegebenenfalls
die Anzahl der Teilstrings. sub(neu,text [,anzahl]) – gibt einen Text zurück, in dem alle Vorkommnisse entsprechend des Suchmusters durch den für neu angegebenen String ersetzt sind. Mit dem Parameter count kann die Anzahl der Ersetzungen definiert werden.
140
5.5 Reguläre Ausdrücke
Einige Anwendungen: Mit dem folgenden Skript werden alle Links, die sich auf einer Internetseite befinden, extrahiert. Verwendet wird die Methode findall(): Listing 5.3: Link-Adressen extrahieren 01 from httplib import * 02 import re 03 def getLinks(url): 04 verb = HTTPConnection(url) 05 verb.request('GET', '/') 06 antw = verb.getresponse() 07 html = antw.read() 08 09 p = re.compile('(?<=href=")(.*?)(?=")') 10 link_matches = p.findall(html) 11 return link_matches
Es werden zunächst die notwendigen Importe durchgeführt. Der Funktion wird die Adresse einer Internetseite übergeben. Zu dieser wird die Verbindung hergestellt und die Seite ausgelesen (Zeile 04 bis 07). Der Quelltext der Seite ist dann in der Variablen html gespeichert. In Zeile 09 wird das Suchmusterobjekt gebildet und schließlich die Suche mit der Methode findall() vorgenommen. Das Suchmuster ist so angelegt, dass es im Text alles findet, was zwischen der Zeichenkette 'href=" und einem einzelnen Anführungszeichen (") steht. Das sind in der Regel die Link-Adressen in einer HTML-Seite. Ruft man die Funktion auf, erhält man folgendes Ergebnis: >>> getLinks('www.densxl.de') ['http://www.densxl.de/', 'styles.css', 'http://www.densxl.de', 'http://www.densxl.de/leistungen', 'http://www.densxl.de/projekte', 'http://www.densxl.de/referenzen', 'http://www.densxl.de/kontakt', 'http://www.densxl.de/impressum', 'mailto:[email protected]']
Man erhält eine Liste mit allen Links auf der Seite – in diesem Fall die Seite meiner Firma. Ein weiteres Skript entfernt bei Postleitzahlen den Buchstaben, der für das Land steht: Listing 5.4: Entfernung des Länderkürzels 01 import re 02 def delLK(text): 03 p = re.compile('D-(?=\d{5})') 04 return p.sub('',text)
Verwendet wird hier die Methode sub(), die in diesem Fall alle dem Suchmuster entsprechenden Vorkommnisse durch einen leeren String ersetzt. Im Folgenden wird die Funktion auf eine Liste mit Ortsangaben angewendet:
Das Suchmuster findet nur die Zeichenkombination D-, wenn sie sich vor einer wenigstens fünfstelligen Zahl befindet. In einer Zeichenkette wie z. B. D-Mark würde es keinen Treffer erzielen.
142
6 Dateien auslesen und schreiben Eine der wesentlichsten Aufgaben in der Datenverarbeitung und damit auch in der Programmierung ist das persistente Speichern von Daten. Wenn etwa Variablen erzeugt und ihnen ein Wert zugewiesen wird, existieren diese nur flüchtig im Arbeitsspeicher des Rechners. Das Gleiche gilt auch für Objekte, die von einer Klasse erzeugt wurden. Spätestens mit dem Ausschalten des Rechners sind diese Objekte und damit ihre Attribute verloren. Will man Daten dauerhaft speichern, müssen sie auf die Festplatte geschrieben werden. Dies geschieht in der Regel nicht ungeordnet, sondern in Form von Dateien. Diese bilden eine Organisationseinheit, die inhaltlich zusammenhängende Daten zusammenfasst. Sie besitzen einen Namen, über den sie identifizierbar und aufrufbar sind. Bei der täglichen Arbeit mit Computern ist der Umgang mit Dateien so selbstverständlich und elementar, dass auch Python eine Möglichkeit bietet, Daten in Dateien zu speichern. Und natürlich gehört dann auch dazu, dass diese Dateien wieder aufgerufen (geöffnet) und die enthaltenen Daten ausgelesen werden können.
6.1
Dateien anlegen und öffnen
Zum Anlegen oder Öffnen einer Datei gibt es in Python die Funktionen open() und file(), die identisch verwendet werden können. Tatsächlich ist open() ein Alias von file(), das erst seit der Python-Version 2.2 existiert. Das bedeutet, dass unabhängig davon, welche der beiden Funktionen man verwendet, immer der gleiche Mechanismus ausgeführt wird, der ein Dateiobjekt zurückliefert. Im Folgenden wird ausschließlich die Funktion file() verwendet. Der Funktion wird als Parameter der Pfad zur Datei übergeben, die geöffnet werden soll. Ein zweiter Parameter bestimmt, in welchem Modus die Datei geöffnet werden soll. Lässt man diesen Parameter weg, wird die Datei im Lesemodus (readonly) geöffnet. Es ist dann nicht möglich, in die Datei hineinzuschreiben. Der folgende Befehl öffnet eine vorhandene Datei zum Lesen, die sich im gleichen Verzeichnis wie das Skript befindet: >>> dat_obj = file('test.txt')
6 Dateien auslesen und schreiben
Ist die Datei nicht vorhanden, wird ein Fehler ausgelöst. Will man eine neue Datei anlegen, muss man den Modus zum Schreiben und einen Dateinamen angeben, der noch nicht im Verzeichnis vorhanden ist: >>> dat_obj = file('frisch.txt', 'w')
Dieses Skript erzeugt eine neue Datei mit dem Namen frisch.txt im gleichen Verzeichnis des Skripts. In das ebenfalls erzeugte Dateiobjekt kann direkt geschrieben werden. Das Schreiben in die erzeugte Datei erfolgt über entsprechende Methoden des Dateiobjekts. Die folgende Tabelle erläutert die Modi, in denen Dateien geöffnet werden können. Modus
Beschreibung
r
Datei kann nur ausgelesen werden. Wenn die Datei nicht existiert, wird ein Fehler ausgelöst.
r+
Datei kann ausgelesen und beschrieben werden. Wenn die Datei nicht existiert, wird ein Fehler ausgelöst.
w
Datei kann beschrieben werden. Bei einer existierenden Datei wird der Inhalt gelöscht. Wenn die Datei nicht existiert, wird sie angelegt.
w+
Datei kann ausgelesen und beschrieben werden. Bei einer existierenden Datei wird der Inhalt gelöscht. Wenn die Datei nicht existiert, wird sie angelegt.
a
Datei kann beschrieben werden. Der vorhandene Inhalt bleibt erhalten. Neue Inhalte werden angefügt. Wenn die Datei nicht existiert, wird sie angelegt.
a+
Datei kann ausgelesen und beschrieben werden. Der vorhandene Inhalt bleibt erhalten. Neue Inhalte werden angefügt. Wenn die Datei nicht existiert, wird sie angelegt.
Tabelle 6.1: Modi für das Öffnen von Dateien Wenn man eine Datei im Binärmodus öffnen will, muss man an das Moduskürzel ein b anhängen. Es ist möglich, den Modus, in dem eine Datei geöffnet wurde, abzufragen. Dies geschieht über das Attribut mode: >>> dat_obj.mode 'r'
Ebenfalls abfragbar ist der Dateiname der geöffneten Datei: >>> dat_obj.name 'frisch.txt'
144
6.2 Methoden zum Schreiben, Lesen, Speichern
Es ist ratsam, beim Zugriff auf Dateien einen try-except-Block zu verwenden, da es immer sein kann, dass eine Datei nicht vorhanden ist oder nicht geöffnet werden kann. Man hat so die Möglichkeit, einen Fehler und das Beenden des Programms abzufangen: try: dat_obj = file(r'c:\tests\daten.txt','r') except: dat_obj = None print 'Datei kann nicht geöffnet werden.'
Mit der Zuweisung von None an die Variable dat_obj wird gewährleistet, dass im Folgenden Zugriffe auf diese Variable nicht mit einem Fehler quittiert werden. Kann das Dateiobjekt nicht gebildet werden, wird auch keine Variable erzeugt. Abfragen wie if dat_obj: ...
wären damit nicht möglich. Durch das Erzeugen der Variable im except-Block kann folgender Code diese Abfrage aber durchführen.
6.2
Methoden zum Schreiben, Lesen, Speichern
Es stehen einige Methoden zur Verfügung, um Dateien auszulesen und in ihnen zu schreiben. Gelesen und geschrieben wird immer ab der aktuellen Cursorposition. Nach dem Öffnen einer Datei befindet sich der Cursor am Anfang der Datei, an der Position 0. Eine Ausnahme bilden die Modi a und a++. Zwar befindet sich auch hier der Cursor nach dem Öffnen an der Position 0, geschrieben wird aber am Ende der Datei. Zunächst die Methoden zum Lesen: read([bytes]) – liest ohne Parameterangabe den gesamten Text der Datei aus. Gibt man für bytes eine Zahl an, so werden von der aktuellen Cursorposition aus die an-
gegebene Anzahl an Bytes (Zeichen) ausgelesen. Der Cursor befindet sich anschließend am Ende des ausgelesenen Bereichs. Zurückgegeben wird immer eine Zeichenkette. readlines() – liest die gesamte Datei ein und gibt eine Liste mit allen Zeilen des Tex-
tes zurück. Das Zeichen für den Zeilenumbruch bleibt erhalten. readline() – liest ab der aktuellen Cursorposition die aktuelle Zeile im Text kom-
plett aus. Anschließend befindet sich der Cursor am Anfang der folgenden Zeile. Zurückgegeben wird eine Zeichenkette, die auch das Zeichen des Zeilenumbruchs enthält. Zum Schreiben stehen folgende Methoden zur Verfügung: write(text) – schreibt den als Parameter übergebenen Text ab der aktuellen Cursor-
position in die Datei. Eventuell vorhandener Text wird dabei überschrieben.
145
6 Dateien auslesen und schreiben
writelines(sequenz) – schreibt den in einer Sequenz aus Zeichenketten enthaltenen
Text ab der aktuellen Cursorposition in die Datei. Zeichen für den Zeilenumbruch werden dabei nicht automatisch erzeugt. Die beiden Methoden schreiben nur in das Dateiobjekt. Die physikalische Datei bleibt davon unberührt. Um den Text auch in die Datei zu schreiben, können zwei Methoden verwendet werden: flush() – schreibt den aktuellen Inhalt des Dateiobjekts in die physikalische Datei.
Die Datei bleibt anschließend geöffnet. close() – schreibt den aktuellen Inhalt des Dateiobjekts in die physikalische Datei
und schließt diese. Das Dateiobjekt bleibt weiter existent, es kann aber nicht mehr lesend oder schreibend darauf zugegriffen werden. Wenn man das Dateiobjekt löschen will, um es aus dem Arbeitsspeicher zu entfernen, muss man den Befehl del verwenden: >>> del dat_obj
Solange das Dateiobjekt noch existiert, kann das Attribut closed abgefragt werden. Es gibt True zurück, wenn die Datei geschlossen wurde, andernfalls False. Es ist auch möglich, mit der print-Anweisung in ein Dateiobjekt zu schreiben. Normalerweise gibt print eine Zeichenkette in die Standardausgabe (meistens die PythonShell) aus. Mithilfe des Doppelpfeils (>>) kann die Ausgabe aber auch in ein Dateiobjekt umgelenkt werden: >>> print >> dat_obj, "Hallo Welt"
Es wird dann nach dem Doppelpfeil das Dateiobjekt benannt und anschließend, durch ein Komma getrennt, die Zeichenkette, die ausgegeben werden soll. Im Wesentlichen entspricht dies der Verwendung der Methode write(). Allerdings ist zu beachten, dass print immer einen Zeilenumbruch an das Ende der Zeichenkette anfügt. Will man dies verhindern, notiert man hinter der Zeichenkette ein weiteres Komma.
6.3
Die Cursorposition
Wie bereits erwähnt, befindet sich der Cursor nach dem Öffnen einer Datei an der Position 0, also am Anfang der Datei. Unter dem Cursor muss man sich die Position in der Datei vorstellen, von der aus alle Aktionen (Methoden) starten. Jede Aktion wiederum ändert die Cursorposition. Wenn z. B. die Methode read() ohne Parameter aufgerufen wird, befindet sich der Cursor anschließend am Ende der Datei. Es gibt zwei Methoden, mit denen man die Cursorposition abfragen bzw. ändern kann:
146
6.4 Praxis: einen Serienbrief erstellen
tell()– fragt die aktuelle absolute Cursorposition ab und gibt diese als Ganzzahl
zurück. Unter Windows kann eventuell eine falsche Position zurückgegeben werden, wenn Dateien mit Unix-Zeilenumbrüchen ausgelesen werden. Mit dem Öffnen im Binärmodus kann dieses Problem umgangen werden. seek(pos,[mode]) – setzt den Cursor an die mit pos bezeichnete Position. Ein zweiter
optionaler Parameter bestimmt, wie die Position angesteuert wird. Der Standardwert dafür ist 0 und sorgt dafür, dass die Position absolut gesetzt wird. Mit dem Wert 1 wird die Position relativ zur aktuellen mit dem Wert 2 relativ zur Endposition gesetzt. Wenn die Datei im Modus a oder a+ geöffnet wurde, wird das Setzen des Cursors für das Schreiben ignoriert. Die aktuelle Cursorposition kann auch verwendet werden, um den Inhalt einer Datei ab dieser Position zu beschneiden. Die lässt sich mit der Methode truncate() durchführen. Sie löscht den gesamten Inhalt ab der aktuellen Position des Cursors bis zum Dateiende. Das ist dann sinnvoll, wenn man ab einer bestimmten Stelle in der Datei den Inhalt überschreiben will, der neue Inhalt aber kürzer ist (weniger Zeichen hat) als der zu überschreibende. Verwendet man in diesem Fall nur die Methode write(), bliebe ein Rest des alten Inhalts erhalten. Führt man vorab truncate() aus, wird zuerst der alte Inhalt komplett entfernt. Danach kann dann mit write() der neue Inhalt geschrieben werden.
6.4
Praxis: einen Serienbrief erstellen
Um den Umgang mit Dateiobjekten und den entsprechenden Methoden zu zeigen, soll eine einfache Serienbrieffunktion implementiert werden. Sie nutzt eine Dateivorlage und erzeugt daraus Briefe, in denen die Anschrift und die Anrede personalisiert sind. Die gesamte Konstruktion benötigt drei Komponenten: 1. Die Briefvorlage 2. Ein Set an Adressen 3. Die Serienbrieffunktion Die Briefvorlage ist eine einfache Textdatei, die der Einfachheit halber im gleichen Verzeichnis abgelegt ist wie das Skript mit der Funktion. Sie soll den Dateinamen vorlage.txt erhalten und folgenden Inhalt haben: An %(vorname)s %(name)s %(strasse)s %(plz)s %(ort)s Sehr %s(anrede)s %(name)s, Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam quis pede. Proin egestas semper eros. Vivamus blandit neque id augue. Maecenas quis leo. Nullam nec orci id enim rhoncus tincidunt. Duis egestas metus sit amet lacus. Etiam gravida tempor mi. Mit freundlichen Grüßen
147
6 Dateien auslesen und schreiben
Mit Formatierungsoperatoren werden die Platzhalter für die Adresse und Anrede im Text platziert. Diese werden durch die Funktion mit Daten gefüllt. Der Datenbestand ist eine Liste mit Dictionarys, die zunächst hardcodiert verwendet wird: adressbuch = [ {'anrede':'Herr','name':'Meyer','vorname':'Klaus', 'strasse':'Wasserweg 7','plz':'12345','ort':'Nichtort'}, {'anrede':'Herr','name':'Schulze','vorname':'Herbert', 'strasse':'Luftstr. 13','plz':'44556','ort':'Falschstadt'}, {'anrede':'Frau','name':'Schmidt','vorname':'Gesine', 'strasse':'Grassteig 16','plz':'89451','ort':'Unsichtbar'}, {'anrede':'Frau','name':'Fritsch','vorname':'Maria', 'strasse':'Hauptallee 24','plz':'55555','ort':'Kleindorf'} ]
Jedes Dictionary stellt einen Datensatz dar, der für die Erstellung eines Briefs verwendet wird. Mit dem folgenden Skript gelangen nun die Daten in die Vorlage und werden als Brief gespeichert: Listing 6.1: Klasse zur Erstellung eines Serienbriefs 01 class SerialLetter(object): 02 def __init__(self, adressen, vorl_dat): 03 # Adressen übernehmen 04 self.adressen = adressen 05 #Vorlage öffnen und einlesen 06 try: 07 vorlage_obj = file(vorl_dat,'r') 08 self.vorlage = vorlage_obj.read() 09 vorlage_obj.close() 10 del vorlage_obj 11 except: 12 self.vorlage = None 13 print 'Vorlage konnte nicht gelesen werden' 14 15 def createLetters(self): 16 if self.vorlage: 17 # Anrede einfügen 18 for adresse in self.adressen: 19 if adresse['anrede'] == 'Herr': 20 adresse['anrede'] = 'geehrter Herr' 21 else: 22 adresse['anrede'] = 'geehrte Frau' 23 24 # Dateiname bilden 25 pfad = 'brief_an_%s.txt' %(adresse['name'].lower()) 26 # Datei erzeugen
Die Serienbrieffunktion ist als Klasse realisiert. Ihr müssen beim Aufruf die Adressdaten und der Pfad zur Vorlagendatei übergeben werden. In der Initialisierungsmethode werden die Attribute adressen (Zeile 04) und vorlage (Zeile 08) mit Werten versehen. Das Attribut vorlage erhält seinen Wert aus der Vorlagendatei, die im tryBlock geöffnet, ausgelesen und anschließend geschlossen wird. Wenn das Auslesen der Vorlagendatei nicht funktioniert, wird ein entsprechender Hinweis gegeben. Die Methode createLetters() prüft zunächst, ob ein Vorlagentext vorhanden ist. Ist das der Fall, wird für jede Adresse die Anrede geschlechtsspezifisch erweitert (Zeile 18–22). Dann wird ein Dateiname mit einem festen Teil und dem jeweiligen Nachnamen gebildet (Zeile 25). Mit diesem Namen wird eine neue Datei angelegt (Zeile 27). Der Text, der in diese Datei geschrieben wird (Zeile 29), bekommt für die Platzhalter in der Vorlage die Daten aus dem Dictionary übergeben. Anschließend wird mit close() der Text in der Datei gespeichert und diese geschlossen. Sollte der Versuch, den Vorlagentext auszulesen, beim Initialisieren des Objekts gescheitert sein, wird eine entsprechende Meldung ausgegeben (Zeile 33). Bildet man von dieser Klasse nun ein Objekt und übergibt die Adressdaten und die Vorlagendatei, kann man anschließend die Methode createLetters() ausführen: >>> sb = SerialLetter(adressbuch,'vorlage.txt') >>> sb.createLetters()
Man erhält dann im gleichen Verzeichnis, in dem auch das Skript liegt, vier neue Textdateien, die Briefe an die Personen aus dem Adressbestand. Die Platzhalter sind nun gefüllt mit den personenbezogenen Daten: An Maria Fritsch Hauptallee 24 55555 Kleindorf Sehr geehrte Frau Fritsch, Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam quis pede. Proin egestas semper eros. Vivamus blandit neque id augue. Maecenas quis leo. Nullam nec orci id enim rhoncus tincidunt. Duis egestas metus sit amet lacus. Etiam gravida tempor mi Mit freundlichen Grüßen
149
6 Dateien auslesen und schreiben
6.5
Beliebige Datentypen speichern
Mit Dateiobjekten lassen sich nur Zeichenketten speichern. Häufig ist man aber in der Situation, dass man andere Datentypen speichern muss, z. B. Listen oder Dictionarys, in denen man Daten gesammelt hat, oder aber Objekte von selbst definierten Klassen, die sich in einem bestimmten Zustand befinden. Man könnte nun versuchen, Listeninhalte auszulesen und als Zeichenkette in eine Datei zu schreiben. Aber das wird sehr schnell sehr aufwändig, wenn man unterschiedliche Datentypen in einer Liste hat oder verschachtelte Listen auf diese Weise speichern will. Diese Arbeit kann man sich zum Glück sparen, da Python ein Modul anbietet, mit dem sich nahezu jeder Datentyp speichern lässt. Dieses Modul trägt den Namen pickle und ist wie gewohnt einfach zu handhaben. Wer sich bei dem Begriff pickle übrigens an mixed pickles erinnert, liegt nicht ganz falsch. Das englische Wort »pickle« bedeutet »einlegen« und so etwas in der Art vollzieht das Modul mit den unterschiedlichen Datentypen, damit sie in einer Textdatei gespeichert werden können. Die Datenobjekte werden zu Bytefolgen serialisiert und damit zu Zeichenketten. Diese können nun wieder wie gewohnt in Dateien gespeichert werden. Folgende Datentypen kann pickle handhaben: None, True und False Ganzzahlen (auch long), Fließkommazahlen, komplexe Zahlen Normale und Unicode-Zeichenketten Tupel, Listen und Dictionarys, die speicherbare Objekte enthalten Funktionen, die auf der obersten Ebene des Moduls definiert sind Standardfunktionen, die auf der obersten Ebene des Moduls definiert sind Klassen, die auf der obersten Ebene des Moduls definiert sind Instanzen dieser Klassen, deren __dict__- oder __setstate__-Methode speicherbar
ist Für einfache Operationen mit dem pickle-Modul sind vor allem zwei Methoden relevant: dump(obj,datei,[protokoll]) – schreibt eine eingelegte Form des Objekts in eine Datei. Mit dem Parameter protokoll kann bestimmt werden, in welcher Form das Objekt eingelegt wird. Wenn für protokoll der Wert 0 übergeben wird, wird ein ASCII-
basiertes Protokoll verwendet. Dies ist auch die Standardeinstellung. Wählt man eine Wert der größer als 0 ist, muss die Datei im Binärmodus geöffnet worden sein. Für protokoll kann auch das Attribut HIGHEST_PROTOCOL verwendet werden, mit dem das höchste verfügbare Protokoll eingestellt wird.. load(datei) – liest die Datei aus und rekonstruiert aus der Zeichenkette das ur-
sprüngliche Objekt und gibt es zurück. Wurde beim Einlegen ein Protokollwert größer als 0 gewählt, muss die Datei im Binärmodus geöffnet worden sein.
150
6.6 Praxis: Adressdaten abspeichern
Beide Methoden operieren mit einem Dateiobjekt, das von der Standardmethode file() erzeugt wurde. Hier ein einfaches Beispiel: >>> >>> >>> >>> >>>
Damit sind die Daten des gleichnamigen Dictionary in der Datei daten.txt gespeichert. Öffnet man diese, wird man allerdings kaum etwas erkennen, da sie im Binärmodus gespeichert wurde: €}q (U_vornameq_U_Hansq_U_nameq_U Mustermannq_U_alterq_K_u. Das ist auch nicht nötig, da sich beim erneuten Einlesen das pickle-Modul um die Wiederherstellung kümmert: >>> speicher = file(r'c:\daten.txt','rb') >>> daten = pickle.load(speicher) >>> daten {'vorname': 'Hans', 'name': 'Mustermann', 'alter': 26}
Wie man sieht, benötigt man lediglich die Methode load(), um das Dictionary wieder herzustellen. Dass hier die Reihenfolge der Schlüssel-Werte-Paare nicht mit jener beim Anlegen des Dictionary übereinstimmt, hat nicht mit dem »Einlegen« der Daten zu tun. Es gilt auch hier, dass die Reihenfolge der Elemente in einem Dictionary irrelevant ist, da der Zugriff über den Schlüssel und nicht über den Index erfolgt. Die Reihenfolge in Listen oder Tupel bleibt beim »Einlegen« erhalten. Das Verwenden eines Protokolls größer als 0 kann den Prozess des Speicherns und Wiederauslesens beschleunigen. Hat man es allerdings mit sehr großen Datenmengen zu tun, ist es ratsam, das Modul cPickle zu verwenden. Es besitzt ebenfalls die Methoden dump() und load(), die wie oben beschrieben verwendet werden. Allerdings fehlt dem Modul das Attribut HIGHEST_PROTOCOL, so dass für den Parameter protocol ein Zahlenwert übergeben werden muss. Das Modul cPickle ist in C geschrieben und etwa 1000 Mal schneller als pickle.
6.6
Praxis: Adressdaten abspeichern
Mit dem Wissen aus dem vorigen Abschnitt kann nun auch eine Klasse definiert werden, die ein Adressbuch als Datei anlegt und dieses mit Einträgen versorgt:
# Adressbuchdatei öffnen pfad = '%s' %(datei) ab_obj = file(pfad,'r') # vorhandene Daten laden ab = load(ab_obj) ab_obj.close() # neue Daten hinzufügen ab.append(eintrag) # Adressbuchdatei im Schreibmodus öffnen ab_obj = file(pfad,'w') # neuen Datenstand hineinschreiben dump(ab,ab_obj)
ab_obj.close() # Ausgabe print 'Neue Adresse angelegt\n' # ggf. Funktion erneut aufrufen erneut = raw_input('Eine weitere Adresse eingeben? (j/n)') if erneut == 'j': cls.eingabe(datei)
Die Klasse Adressbuch besitzt zwei Klassenmethoden, von denen eine für das Anlegen der Adressbuchdatei zuständig ist (anlegen()) und die andere (eingabe()) Adresseinträge in eine Adressbuchdatei einfügt. Das Anlegen der Datei erfolgt mit der Methode file() (Zeile 14). Der Dateiname kann an die Methode raw_input() übergeben werden (Zeile 10). Mit der Methode dump() des Moduls pickle wird die zu Beginn erstellt leere Liste (Zeile 07) in die Datei geschrieben (Zeile 16). Damit ist der Dateityp, der die Adressdaten aufnehmen soll, festgelegt. Anschließend wird eine Abfrage gemacht, ob direkt Daten in die neue Datei eingegeben werden sollen (Zeile 22). Wird dies bejaht, erfolgt direkt der Aufruf der Methode eingabe() (Zeile 23– 25). Diese Methode beginnt mit der Sammlung der einzelnen Adressbestandteile (Zeile 30–37). Diese Daten werden anschließend in ein Dictionary übertragen (Zeile 40). Dieses Dictionary soll nun in die Liste, die in der Datei abgespeichert ist, eingetragen werden. Es muss also zunächst die Datei geöffnet und mit der Methode load() die Liste rekonstruiert werden (Zeile 48–53). Dann kann der neue Datensatz (das Dictionary) angefügt werden (Zeile 55). Schließlich muss die gesamte Liste wieder in die Datei hineingeschrieben werden. Also wird die Datei erneut geöffnet (Zeile 58) und mit der Methode dump() werden die serialisierten Daten hineingeschrieben (Zeile 60). Da man eventuell weitere Adressen eintragen möchte, bietet die Methode eine Abfrage, ob dies geschehen soll. Wird dies bejaht, wird die Methode erneut aufgerufen (Zeile 66–68).
153
6 Dateien auslesen und schreiben
Ein Programmablauf sieht dann folgendermaßen aus: >>> ab = Adressbuch() >>> ab.anlegen() Neue Adressdatei anlegen Bitte einen Dateinamen eingeben: a_buch Neue Adressdatei angelegt. Wollen Sie sofort Adressen eingeben? (j/n)j Neue Adresse eintragen: Anrede: Herr Namen: Beispielhaft Vorname: Klaus Strasse: Am Beispiel 3 Postleitzahl: 55555 Ort: Beispielburg Neue Adresse angelegt Wollen Sie eine weitere Adresse eingeben? (j/n)j Neue Adresse eintragen: Anrede: Frau Namen: ....
6.7
String-Objekt als Zwischenspeicher
Nicht immer ist es effektiv, direkt in eine Datei zu schreiben. Dies bedeutet immer einen Zugriff auf die Festplatte, der vergleichsweise langsam ist. Will man das umgehen, kann man ein String-Objekt als Zwischenspeicher nutzen. Sinnvoll ist das vor allem dann, wenn eine Anwendung sehr häufig schreibend oder lesend auf eine Datei zugreifen müsste. Das String-Objekt wird von dem Modul StringIO (IO steht für in/out) zur Verfügung gestellt, das nur eine Klasse mit dem gleichen Namen besitzt. Dieses Objekt kann wie ein Dateiobjekt verwendet werden, das mit der Methode file() erstellt wurde. Das heißt, die Methoden des Dateiobjekts, wie sie in den vorigen Abschnitten beschrieben wurden, sind auch auf das String-Objekt anwendbar: >>> >>> >>> >>> >>> 'In
154
from StringIO import StringIO str_obj = StringIO() str_obj.write('In den Zwischenspeicher') str.obj.seek(0) str_obj.read() den Zwischenspeicher'
6.7 String-Objekt als Zwischenspeicher
Anders als beim Dateiobjekt ist allerdings kein Speichern notwendig. So ist die Methode flush() zwar ausführbar, sie hat aber keine Auswirkungen. Die Methode close() verhält sich teilweise wie bei einem Dateiobjekt. Sie schließt das String-Objekt, so dass keine Lese- oder Schreiboperationen mehr möglich sind. Speichern findet hier allerdings auch nicht statt. Neben den Methoden des Dateiobjekts besitzt ein String-Objekt noch die Methode getvalue(). Sie ermöglicht das Auslesen des Inhalts unabhängig von der aktuellen Cursorposition. Es wird immer der gesamte Inhalt zurückgeliefert. Interessant ist auch das Attribut len, das die Anzahl der Bytes bzw. der Zeichen im String-Objekt angibt. Damit hat man immer Kenntnis über die aktuelle Länge der im Objekt abgelegten Zeichenkette. Beim Erstellen kann dem String-Objekt bereits eine Zeichenkette übergeben werden: >>> str_obj2 = StringIO('Schon da')
Die Cursorposition ist dann aber trotzdem 0, sodass sofort ausgelesen werden kann: >>> str_obj2.read() 'Schon da'
Die zu Beginn übergebene Zeichenkette kann über das Attribut buf abgefragt werden. Dieses Attribut ändert seinen Wert auch dann nicht, wenn zusätzlicher Text in das Objekt geschrieben wird: >>> str_obj2.write(' und noch mehr') >>> str_obj2.getvalue() 'Schon da und noch mehr' >>> str_obj2.buf 'Schon da'
155
7
Arbeiten mit dem Laufzeitund dem Betriebssystem
Ein Python-Skript befindet sich nicht im luftleeren Raum. Bei seiner Ausführung wird der Python-Interpreter aktiv. Allgemeiner gesprochen bildet der Interpreter das Laufzeitsystem, in dem das Skript abläuft. Mit dem Modul sys stehen einige Attribute und Methoden zur V6erfügung, die es ermöglichen, Informationen über das Laufzeitsystem zu ermitteln bzw. dieses in seinem Verhalten zu beeinflussen. Ein Python-Skript wird aber immer auch auf einem Computer ausgeführt und dieser Computer hat ein Betriebssystem. Will man Operationen im Dateisystem ausführen, arbeitet man in und mit dem Betriebssystem. Eine Schnittstelle dazu bildet das Modul os. Mit ihm ist es z. B. möglich, Verzeichnisse auszulesen oder anzulegen, Zugriffsrechte abzufragen und zu setzen oder grundlegende Informationen über das Betriebssystem zu erhalten. Die beiden Module sys und os sind Gegenstand dieses Kapitels.
7.1
Das Modul sys
Mit dem Modul sys erhält man eine Reihe von Attributen, die Informationen über die Laufzeitumgebung wiedergeben. Die folgende Tabelle listet die wesentlichen auf: Attribut
Erläuterung
builtin_module_names
Liefert die Namen der eingebauten Python-Module als Tupel.
byteorder
Die Byte-Order (Reihenfolge, in der Bytes im Arbeitsspeicher gespeichert werden) für einfache Zahlenwerte. Diese ist systemabhängig. Mögliche Werte sind little für ein Little-Endian-System und big für ein Big-Endian-System. Little- und Big-Endian-Systeme sind zwei verschiedene Arten, Bytes im Arbeitsspeicher zu organisieren (für mehr Informationen siehe http://de.wikipedia.org/wiki/BigEndian).
copyright
Enthält eine Zeichenkette mit den Copyrights von Python.
Tabelle 7.1: Attribute des Moduls sys
7 Arbeiten mit dem Laufzeit- und dem Betriebssystem
Attribut
Erläuterung
dllhandle
Enthält das Handle (ein ganzzahliger Wert als Referenz) der PythonDLL (nur Windows).
executable
Der Pfad des Python-Interpreters
hexversion
Ganzzahl, die die Python-Version enthält. Mit der Standardfunktion hex() kann der erhaltene Wert in eine Hexadezimalzahl umgewandelt werden.
maxint
Die größtmögliche Ganzzahl. Alle größeren Zahlen werden als LongInteger interpretiert.
maxunicode
Der größte unterstützte Unicode-Zeichencode
modules
Liefert ein Dictionary mit allen Standardmodulen von Python. Der Schlüssel ist der jeweilige Modulname, der Wert das Modul selbst.
path
Eine Liste mit den Suchpfaden für Module
platform
Eine Zeichenkette, die die Betriebssystemplattform beschreibt
prefix
Der Pfad zu dem Verzeichnis, in dem Python installiert ist
version
Eine Zeichenkette mit Versionsinformationen des Interpreters
version_info
Ein Tupel, das mit fünf Elementen die Version der Interpreters wiedergibt
winver
Die Python-Version, wie sie in die Registry auf Windows-Rechnern eingetragen wird
Tabelle 7.1: Attribute des Moduls sys (Forts.) Einige der Attribute sind bereits in anderen Zusammenhängen aufgetaucht und besprochen worden. Hier ein paar Beispiele dafür, wie die Informationen tatsächlich aussehen, die man über sie erhält: >>> sys.executable 'C:\\Python25\\pythonw.exe' >>> sys.maxint 2147483647 >>> sys.modules {'tkFileDialog': <module 'tkFileDialog' from 'C:\Python25\lib\libtk\tkFileDialog.pyc'>, 'random': <module 'random' from 'C:\Python25\lib\random'>, ... ,'time': <module 'time' (built-in)>, 'idlelib.select': None, 'idlelib.run': <module 'idlelib.run' from 'C:\Python25\lib\idlelib\run.pyc'>} >>> sys.path ['C:\\Python25\\Lib\\idlelib', 'C:\\Python25\\python25.zip', 'C:\\Python25\\DLLs', 'C:\\Python25\\lib', 'C:\\Python25\\lib\\plat-win', 'C:\\Python25\\lib\\lib-tk', 'C:\\Python25', 'C:\\Python25\\lib\\site-packages', 'C:\\Python25\\lib\\site-
Einige weitere Informationen können mit Funktionen abgerufen werden. Darüber hinaus kann mit anderen Funktionen das Verhalten des Interpreters beeinflusst werden. Hier sollte man vorsichtig herangehen und keine extremen Werte angeben, da man sonst einen Systemabsturz riskiert. Die folgende Tabelle erläutert die Funktionen. Funktion
Erläuterung
getcheckinterval()
Gibt die Frequenz der Prüfung auf periodische Abläufe zurück. Der Standardwert ist 100.
getdefaultencoding()
Gibt das Stringencoding des Interpreters zurück. Der Standardwert ist ascii
getfilesystemencoding()
Gibt das Encoding des Dateisystems zurück, das für die Umwandlung von Dateinamen verwendet wird.
getrecursionlimit()
Gibt die Anzahl der maximal erlaubten Rekursionen zurück. Der Standardwert ist 1000.
getrefcount(obj)
Gibt die Anzahl der Referenzen eines Objekts zurück.
setcheckinterval(wert)
Setzt die Frequenz, mit der auf periodische Abläufe geprüft wird. Ein höherer Wert als der Standardwert 100 kann die Performance des Interpreters erhöhen.
setrecursionlimit(wert)
Setzt die Anzahl der maximal erlaubten Rekursionen. Ein zu hoher Wert kann das System zum Absturz bringen.
Tabelle 7.2: Funktionen des Moduls sys
7.1.1
Argumente für Skripte, Skripte beenden
An Skripte, die nicht über die Python-Shell ausgeführt, sondern in der System-Shell oder unter Windows in der Eingabeaufforderung aufgerufen werden, kann man Werte übergeben. Der Aufruf eines Skripts erfolgt über die Eingabe seines Dateinamens. Werte werden mit Leerzeichen getrennt an den Namen angehängt. Innerhalb des Skripts sind diese Werte über sys.argv abrufbar. Diese Variable ist eine Liste, die als erstes Element immer den Pfad des Skripts enthält. Die gegebenenfalls übergebenen Werte finden sich als weitere Elemente in dieser Liste.
159
7 Arbeiten mit dem Laufzeit- und dem Betriebssystem
Darüber hinaus kann man solche Skripte mit sys.exit([fehlercode]) beenden, wenn der Ablauf dies notwendig macht. Dieser Funktion kann eine Ganzzahl als Fehlercode übergeben werden. Der Standardwert ist 0 und zeigt an, dass kein Fehler aufgetreten ist. Man kann zusätzlich eine Funktion definieren, die beim Beenden des Skripts aufgerufen wird. Mit sys.exitfunc = funktionsname
wird die Funktion als Beenden-Funktion festgelegt. Die Funktion darf keine Parameter haben. Folgendes Beispiel zeigt, was das Modul sys in Skripten leisten kann. Das Skript trägt den Namen register.py: Listing 7.1: Möglichkeiten des sys-Moduls 01 02 03 04 05 06 07 08 09 10 11 12
import sys def hinweis(): print "Bitte Vor- und Nachname angeben!" if len(sys.argv) < 2: sys.exitfunc = hinweis sys.exit()
text = """Vielen Dank %s %s. Sie wurden registriert.""" print text %(sys.argv[1], sys.argv[2])
Das Skript prüft, ob ein Name übergeben wurde, und gibt diesen aus. Wurde aber kein Name übergeben, wird das Skript beendet. Die Prüfung findet in Zeile 06 statt, indem die Anzahl der Elemente in sys.argv abgefragt wird. Ist diese kleiner als zwei, wurden keine Werte an das Skript übergeben. Es befindet sich nur der Pfad des Skripts in der Liste. Es wird dann eine Ausstiegsfunktion festgelegt (Zeile 07) und das Skript beendet (Zeile 08). Wurde aber ein Name übergeben, wird dieser in eine Zeichenkette eingefügt und diese ausgegeben (Zeile 12). Das Skript leistet natürlich keine wirklichen Aufgaben, zeigt aber die in diesem Abschnitt besprochenen Möglichkeiten exemplarisch auf. Ein Aufruf des Skripts über eine System-Shell sieht folgendermaßen aus: register.py Bitte Vor- und Nachname angeben!
Ohne Argumente wird das Skript beendet und die Ausstiegsfunktion aufgerufen.
160
7.2 Das Modul os
register.py Peter Walerowski Vielen Dank Peter Walerowski. Sie wurden registriert.
Mit Argumenten wird nur der Text ausgegeben.
7.1.2
Standardein- und -ausgabe
Mit sys.stdin und sys.stdout stehen zwei Attribute zur Verfügung, die mit der Standardein- bzw. -ausgabe verbunden sind. Wenn Eingaben über die Funktionen input() oder raw_input() empfangen werden, gelangen diese zunächst in die Standardeingabe. Die Standardausgabe nimmt print-Anweisungen entgegen oder das Ergebnis der Auswertung von Ausdrücken. Die Attribute sys.stdin und sys.stdout sind Dateiobjekte (auch Pseudodateien genannt), die die Methoden des Dateiobjekts besitzen (siehe Abschnitt 6.2). So kann man mit der Methode write() in die Standardausgabe schreiben: >>> sys.stdout.write('Standardausgabe') Standardausgabe
Dies entspricht in etwa der print-Anweisung. Man kann aber auch die Standardausgabe umlenken, z. B. in eine Datei, so dass printAnweisungen nicht mehr ausgegeben, sondern direkt in die Datei geschrieben werden. Diese Möglichkeit kann z. B. für das Protokollieren von Fehlern im Hintergrund genutzt werden: >>> dat_obj = file('ausgabe.txt','w') >>> sys.stdout = dat_obj >>> print 'Standardausgabe' >>>
7.2
Das Modul os
Während sich der Python-Interpreter darum kümmert, dass die Ausführung eines Programms oder Skripts unter dem Betriebssystem funktioniert, also die Übersetzung des Quellcodes in die Maschinensprache vornimmt, muss man für Operationen im Dateisystem selbst sorgen. Hier geht es um die Verwaltung, also das Anlegen, Auffinden, Löschen von Dateien und Verzeichnissen. Die Problematik besteht darin, dass Python auf unterschiedlichen Plattformen läuft, die unterschiedliche Arten der Dateiverwaltung haben. Mit dem Modul os stehen Attribute und Methoden zur Verfügung, die für alle Plattformen in der gleichen Weise verwendet werden können, so dass man seine Programme plattformübergreifend entwickeln kann. Die Zusammenarbeit mit dem jeweiligen Betriebssystem übernimmt dieses Modul. Das Modul os besitzt ein Untermodul mit dem Namen path, das für die Arbeit mit Pfaden herangezogen werden kann.
161
7 Arbeiten mit dem Laufzeit- und dem Betriebssystem
7.2.1
Konstanten für die Dateinamenverwaltung
Die verschiedenen Betriebssysteme verwenden unterschiedliche Zeichen für die Angaben von Pfaden und haben auch sonst weitere Eigenarten, die bei der Arbeit mit Dateien und Verzeichnissen von Bedeutung sind. Während z. B. unter Windows Pfadelemente mit dem Zeichen \ (Backslash) getrennt werden, verwendet man unter Linux und MacOS X das Zeichen / (Slash). Das Modul os stellt einige Konstanten zur Verfügung, die man verwenden kann, um plattformübergreifende Skripte zu schreiben. Die folgende Tabelle stellt sie dar (die Spalte »Plattform« enthält die Information, auf welchen Betriebssystemen die Konstante verwendet werden kann): Konstante
Erläuterung
Plattform
name
Enthält ein Kürzel für das Betriebssystem, z. B. nt für Windows NT, 2000 sowie XP oder 'posix' für Unix oder Linux.
alle
curdir
Das Zeichen für das aktuelle Verzeichnis
alle
pardir
Das Zeichen für das übergeordnete Verzeichnis
alle
sep
Das Zeichen für den Pfadseparator
alle
extsep
Das Zeichen, das Dateinamen und -erweiterung trennt
alle
altsep
Alternatives Zeichen für den Pfadseparator
Windows, MacOS
pathsep
Trennzeichen, das in der Systemvariablen PATH die Pfadangaben trennt
alle
linesep
Das oder die Zeichen, die für den Zeilenumbruch verwendet werden
alle
defpath
Der Suchpfad für ausführbare Dateien
alle
devnull
Der Pfad der virtuellen Gerätedatei
Linux
Tabelle 7.3: Konstanten des Moduls os Anhand einiger Beispiele wird im Folgenden die Verwendung einiger Konstanten demonstriert – zunächst die Bildung eines Pfads aus einer Liste mit der Konstante sep: >>> pe = ['elemente','des','pfads'] >>> os.sep.join(pe) 'elemente\\des\\pfads'
Mit der Methode join() des String-Objekts werden die Elemente der Liste zusammengefügt. Die Konstante sep liefert das Trennzeichen zwischen den Elementen. Unter Unix sieht das Ergebnis folgendermaßen aus: 'elemente/des/pfads'
Die Konstante sep garantiert, dass bei der Bildung des Pfads das richtige Trennzeichen eingefügt wird.
162
7.2 Das Modul os
Mit Pfaden arbeiten Es ist empfehlenswert, bei der plattformübergreifenden Entwicklung Pfadelemente in Listen zu verwalten. Man kann Elemente mit Listenoperationen hinzufügen oder entfernen und schnell den Pfad mit der Methode join() zusammenfügen. Erhält man einen Pfad als Zeichenkette kann diese mit der Methode split() in eine Liste transferiert werden: >>> pfad = 'c:\\elemente\\des\\pfads' >>> pfad.split(os.sep) ['c:', 'elemente', 'des', 'pfads']
Auch hier spielt wieder os.sep die entscheidende Rolle für eine plattformübergreifende Notation. Unter Unix notiert man: >>> pfad = '/elemente/des/pfads' >>> pfad.split(os.sep) ['', 'elemente', 'des', 'pfads']
Zwar unterscheiden sich die Listen im jeweils ersten Element. Geht man allerdings davon aus, dass das Programm den Pfad von außen erhält, z. B. durch eine Benutzereingabe, kann für die jeweilige Plattform aus der jeweiligen Liste in jedem Fall wieder ein korrekter Pfad erzeugt werden. Häufig arbeit man mit relativen Pfaden und muss vom aktuellen Arbeitsverzeichnis aus eine Ebene nach oben wechseln. Hier ein Beispiel, wie man das mit der Konstante pardir erledigt: >>> pe = ['eine','ebene','hoch'] >>> pe.insert(0,os.pardir) >>> os.sep.join(pe) '..\\eine\\ebene\\hoch'
Bevor der Pfad gebildet wird, wird in die Liste der Pfadelemente die Konstante pardir eingefügt. Damit wird für jede Plattform das richtige Zeichen für das Verzeichnis auf höherer Ebene eingefügt.
7.2.2
Orientieren und prüfen
Der erste Schritt, um mit einem Programm Daten sinnvoll und organisiert zu verwalten, ist, eine geeignete Position für die Ablage von Daten zu finden. Natürlich hat man es nicht in der Hand, wo der Benutzer das Programm ablegt oder wo auf dem fremden Rechner der Python-Interpreter installiert ist. Mit einigen Methoden des Moduls os kann man sich aber einen Überblick verschaffen:
163
7 Arbeiten mit dem Laufzeit- und dem Betriebssystem
Methode
Erläuterung
getcwd()
Gibt das aktuelle Arbeitsverzeichnis zurück. Im interaktiven Modus ist dies das Verzeichnis der Python-Installation, beim Ausführen eines Skripts ist es das gleiche Verzeichnis, in dem das Skript abgelegt ist. Das aktuelle Arbeitsverzeichnis ist der Ausgangspunkt für relative Pfade.
chdir(pfad)
Ändert das Arbeitsverzeichnis. Für pfad kann eine Zeichenkette mit relativem oder absolutem Pfad angegeben werden.
listdir(pfad)
Gibt eine Liste mit den Namen der Dateien und Verzeichnisse zurück, die sich im mit pfad angegebenen Verzeichnis befinden.
path.isdir(pfad)
Gibt True zurück wenn pfad auf ein Verzeichnis zeigt, andernfalls False.
path.isfile(pfad)
Gibt True zurück, wenn pfad auf eine Datei zeigt, andernfalls False.
path.islink(pfad)
Gibt True zurück, wenn pfad auf einen symbolischen Link (Referenz auf ein Verzeichnis oder eine Datei) zeigt, andernfalls False. Bei Systemen, die symbolische Links (Windows) nicht kennen, wird False zurückgegeben.
path.ismount(pfad)
Gibt True zurück, wenn pfad auf einen Mount Point zeigt, andernfalls False. Bei Systemen, die Mount Points nicht kennen (Windows), wird False zurückgegeben.
path.isabs(pfad)
Gibt True zurück, wenn pfad ein absoluter Pfad ist, andernfalls False
Tabelle 7.4: Methoden zum Prüfen der Verzeichnisstruktur Das folgende Beispiel zeigt die Verwendung einiger Methoden, die in der Tabelle besprochen wurden. Es untersucht das aktuelle Arbeitsverzeichnis (in diesem Fall das Arbeitsverzeichnis des Interpreters) und teilt Dateien und Verzeichnisse und unterschiedliche Listen: >>> dl = [] >>> vl = [] >>> for dat in os.listdir(os.getcwd()): if os.path.isfile(dat): dl.append(dat) else: vl.append(dat) >>> dl ['ausgabe.txt', 'LICENSE.txt', 'msvcr71.dll', 'NEWS.txt', 'PIL-wininst.log', 'python.exe', 'python25.dll', 'pythonw.exe', 'README.txt', 'RemovePIL.exe', 'test.txt', 'w9xpopen.exe'] >>> vl ['DLLs', 'Doc', 'include', 'Lib', 'libs', 'Scripts', 'tcl', 'Tools']
164
7.2 Das Modul os
7.2.3
Anlegen, ändern, löschen
Das Modul os bietet einige Methoden an, mit denen Verzeichnisse manipuliert werden können. Teilweise können diese Methoden auch mit Dateien umgehen. Tatsächlich ist es so, dass os ein ganzes Set von Methoden zum Anlegen, Öffnen und Bearbeiten von Dateien besitzt. Man kommt aber in der Regel schneller und effektiver zum Ziel, wenn man die Standardfunktion file() zum Öffnen von Dateien verwendet (siehe Kapitel 6). Deshalb werden hier nur die Methoden von os behandelt, die mit Verzeichnissen arbeiten. Die folgende Tabelle listet sie auf: Methode
Erläuterung
mkdir(pfad,[modus])
Erzeugt ein Verzeichnis am angegebenen Pfad. Der Parameter modus regelt die Zugriffsrechte, der Standardwert ist 0777 (siehe Abschnitt 7.2.4).
makedirs(pfad,[modus])
Erzeugt ein neues Verzeichnis und gegebenenfalls alle in der Pfadangabe nicht vorhandenen Verzeichnisse. Der Parameter modus regelt die Zugriffsrechte, der Standardwert ist 0777 (siehe Abschnitt 7.2.4).
remove(pfad)
Löscht die Datei mit dem angegebenen Pfad.
rmdir(pfad)
Löscht das leere Verzeichnis mit dem angegebenen Pfad.
removedirs(pfad)
Löscht leere Verzeichnisse im gesamten angegebenen Pfad.
rename(pfad_alt,pfad_neu)
Benennt Dateien und Verzeichnisse um. Kann auch zum Verschieben von Dateien verwendet werden.
renames(pfad_alt,pfad_neu)
Benennt Dateien und Verzeichnisse um. Kommen in pfad_neu Namen vor, zu denen keine Verzeichnisse existieren, werden diese angelegt. Kann auch zum Verschieben von Dateien verwendet werden.
Tabelle 7.5: Methoden zum Umgang mit Verzeichnis (und teilweise auch Dateien)
Hinweis Die Methoden des Moduls os, die Dateien öffnen können, sind in der Python Library Reference im Abschnitt 14.1.2 beschrieben, erreichbar unter http://docs.python.org/lib/os-newstreams.html
Häufig will man Dateien, die vom Programm angelegt werden, in separate Verzeichnisse speichern oder man überlässt es dem Benutzer, anzugeben, wo Dateien gespeichert werden sollen.
165
7 Arbeiten mit dem Laufzeit- und dem Betriebssystem
Die folgenden Beispiele zeigen die Verwendung der Methoden des os-Moduls, die im Dateisystem operieren: >>> os.mkdir('neue_daten')
Gibt man nur einen Namen für ein Verzeichnis an, wird es im aktuellen Arbeitsverzeichnis angelegt. Mit relativen Pfaden legt man ein Verzeichnis in einem (vorhandenen) Unterverzeichnis des aktuellen Arbeitsverzeichnisses an: >>> os.mkdir('neue_daten/tabellen')
Dies funktioniert aber nur dann, wenn das Unterverzeichnis bereits existiert. Ist dies nicht der Fall, kann man mit makedirs() dafür sorgen, dass in jedem Fall alle notwendigen Verzeichnisse angelegt werden: >>> os.makedirs('alte_daten/tabellen')
Während das Löschen von Dateien oder Verzeichnissen mit remove() bzw. rmdir() recht einfach zu bewältigen ist, da man lediglich den (relativen oder absoluten) Pfad zum Verzeichnis oder zur Datei angeben muss, sollte man auf removedirs() einen genaueren Blick werfen: >>> os.removedirs(r'c:\a\b\c\d')
Bei einem solchen Befehl würden die Verzeichnisse a, b, c und d gelöscht, wenn sie leer sind. Leer bedeutet aber, dass sich weder Dateien noch andere Verzeichnisse in dem fraglichen Verzeichnis befinden. Aber zum Zeitpunkt der Ausführung befindet sich in Verzeichnis a Verzeichnis b und darin Verzeichnis c und Letzteres enthält das Verzeichnis d. Einzig das Verzeichnis d ist leer. Trotzdem aber löscht removedir() alle Verzeichnisse (sofern a, b und c außer den genannten keine weiteren Daten enthält). Das gelingt deshalb, weil die Ausführung den Pfad quasi von hinten abarbeitet. Zuerst wird das Verzeichnis d gelöscht, damit ist dann Verzeichnis c leer und kann gelöscht werden. Danach ist Verzeichnis b leer und kann gelöscht werden usw. Das Umbenennen von Dateien oder Verzeichnissen ist auch keine besonders komplexe Aufgabe. Interessant ist aber, dass man mit rename() und renames() Dateien und Verzeichnisse auch verschieben kann. Wenn es z. B. eine Datei mit folgendem Pfad gibt: c:\a\b\c\d\die_datei.txt dann kann sie mit >>> rename(r'c:\a\b\c\d\die_datei.txt', r'c:\a\b\c\die_datei.txt')
in das höher gelegene Verzeichnis verschoben werden. Eine andere Möglichkeit besteht darin, mit >>> rename(r'c:\a\b\c\die_datei.txt', r'c:\a\b\c1\die_datei.txt')
166
7.2 Das Modul os
die Datei in ein anderes Verzeichnis auf gleicher Ebene zu verschieben. Die Methode renames() stellt sicher, dass das Verzeichnis c1 erstellt wird, sollte es nicht existieren: >>> renames(r'c:\a\b\c\die_datei.txt', r'c:\a\b\c1\die_datei.txt')
7.2.4
Datei- und Verzeichnisattribute auslesen
Neben der eigentlichen Datei speichert das Betriebssystem noch einige weitere Informationen zu den Dateien wie z. B. das Datum der Erstellung oder des letzten Zugriffs. Die folgende Tabelle listet die Methoden auf, mit denen man diese Informationen abfragen kann. Methode
Erläuterung
path.exists(pfad)
Gibt True zurück, wenn der Pfad vorhanden ist, also die gesuchte Datei oder das gesuchte Verzeichnis existiert.
path.realpath(pfad)
Gibt den absoluten Pfad einer Datei oder eines Verzeichnisses zurück.
path.getsize(pfad)
Gibt die Dateigröße einer Datei in Bytes zurück.
path.getatime(pfad)
Gibt den Zeitpunkt des letzten Zugriffs zurück. Die zurückgegebene Zahl ist die Anzahl der Sekunden seit dem 1.1.1970.
path.getmtime(pfad)
Gibt den Zeitpunkt der letzten Änderung zurück. Die zurückgegebene Zahl ist die Anzahl der Sekunden seit dem 1.1.1970.
path.getctime(pfad)
Gibt den Zeitpunkt der Erstellung zurück. Auf manchen Unix-Systemen ist dies der Zeitpunkt der letzten Änderung. Die zurückgegebene Zahl ist die Anzahl der Sekunden seit dem 1.1.1970.
stat(pfad)
Gibt ein Objekt zurück, das die meisten Informationen über eine Datei oder ein Verzeichnis enthält. Diese Informationen sind entweder als Tupel auslesbar oder über Attribute des Objekts. Die wichtigsten Attribute sind: st_size – Größe der Datei in Byte st_atime – Zeitpunkt des letzten Zugriffs st_mtime _ Zeitpunkt der letzten Änderung st_ctime – Zeitpunkt der Erstellung
Die Zeitangaben erfolgen in Anzahl der Sekunden seit dem 1.1.1970.
Tabelle 7.6: Methoden zum Auslesen von Datei- und Verzeichnisattributen Das folgende Beispielskript gibt eine Dateitabelle mit Informationen aus und verwendet dabei einige Methoden aus Tabelle 7.6:
167
7 Arbeiten mit dem Laufzeit- und dem Betriebssystem
Listing 7.2: Skript zur Erstellung einer Liste des Verzeichnisinhalts 01 import os, time 02 03 def listDir(dir): 04 # Prüfen, ob Verzeichnis existiert 05 if os.path.exists(dir): 06 os.chdir(dir) 07 else: 08 print 'Verzeichnis nicht vorhanden' 09 return 10 # Schleife über alle Elemente im Verzeichnis 11 for item in os.listdir(dir): 12 name = item 13 # Datum umwandeln in hiesiges Format 14 mt = time.localtime(os.path.getmtime(item)) 15 # Führende 0 (Null) für Datum hinzufügen 16 f_mt = [str(num).zfill(2) 17 for num in mt] 18 # Datums-String bilden 19 m_time = '%s.%s.%s %s:%s:%s' %(f_mt[2], 20 f_mt[1], 21 f_mt[0], 22 f_mt[3], 23 f_mt[4], 24 f_mt[5]) 25 26 if os.path.isdir(item): 27 # Für Verzeichnisse 9 Leerzeichen einfügen 28 size = ' '*9 29 typ = 'Ordner' 30 else: 31 # Für Dateien die Größe bestimmen 32 size = '%.2f KB' %(os.path.getsize(item)/1024.0) 33 typ = 'Datei' 34 35 #Leerzeichen zwischen Name und Größe berechnen 36 fill = ' '*(20-len(name)+10-len(size)) 37 #Zeile ausgeben 38 print '%s%s%s\t%s\t%s' %(name,fill,size,m_time,typ)
Die Funktion prüft zunächst, ob der angegebene Pfad zum Verzeichnis existiert (Zeile 05), und bricht im negativen Fall die Ausführung mit einer entsprechenden Meldung ab. Wenn das Verzeichnis existiert, wird es als aktuelles Arbeitsverzeichnis deklariert (Zeile 06). Dann kann mit listdir() der gesamte Inhalt ausgelesen und überprüft werden. Es werden vom Verzeichnisinhalt der Name (Zeile 12) und das Datum der letzten Änderung (Zeile 13) festgehalten. Größe und Typbezeichnung werden für
168
7.2 Das Modul os
Dateien und Verzeichnisse unterschiedlich bestimmt (Zeile 26–33). Mit den ermittelten und erstellten Daten wird dann eine Ausgabezeile erstellt (Zeile 38). Damit die Ausgabe gut lesbar ist, wird das Datum formatiert (Zeile 16–19) und für gleiche Abstände eine Anzahl von Füllzeichen berechnet (Zeile 36). Führt man die Funktion für das Verzeichnis der Python-Installation aus, erhält man (unter Windows) folgendes Ergebnis: >>> listDir(r'c:\python25') DLLs Doc include Lib libs LICENSE.txt 13.49 msvcr71.dll 340.00 NEWS.txt 86.50 PIL-wininst.log 34.52 python.exe 23.50 python25.dll 2060.00 pythonw.exe 24.00 README.txt 55.36 RemovePIL.exe 60.00 Scripts tcl Tools w9xpopen.exe 4.50
Mit nur wenigen Zeilen Python-Code lässt sich also eine übersichtliche Verzeichnisansicht erstellen. Die gleiche Ausgabe, erstellt aber mithilfe der Methode stats(), erhält man mit folgendem Skript: Listing 7.3: Dateiinformation über os.stat() ermitteln 01 import os, time 02 03 def listDir(dir): 04 # Prüfen, ob Verzeichnis existiert 05 if os.path.exists(dir): 06 os.chdir(dir) 07 else: 08 print 'Verzeichnis nicht vorhanden' 09 return 10 # Schleife über alle Elemente im Verzeichnis 11 for item in os.listdir(dir): 12 # Informationen auslesen 13 info = os.stat(item)
169
7 Arbeiten mit dem Laufzeit- und dem Betriebssystem
name = item # Datum umwandeln in hiesiges Format mt = time.localtime(info.st_mtime) # Führende 0 (Null) für Datum hinzufügen f_mt = [str(num).zfill(2) for num in mt] # Datums-String bilden m_time = '%s.%s.%s %s:%s:%s' %(f_mt[2], f_mt[1], f_mt[0], f_mt[3], f_mt[4], f_mt[5]) if os.path.isdir(item): # Für Verzeichnisse 9 Leerzeichen einfügen size = ' '*9 typ = 'Ordner' else: # Für Dateien die Größe bestimmen size = '%.2f KB' %(info.st_size/1024.0) typ = 'Datei' #Leerzeichen zwischen Name und Größe berechnen fill = ' '*(20-len(name)+10-len(size)) #Zeile ausgeben print '%s%s%s\t%s\t%s' %(name,fill,size,m_time,typ)
Zugriffsrechte Ebenfalls zu den Datei- und Verzeichnisattributen gehören die Zugriffsrechte. Sie unterscheiden sich aber deutlich auf den verschiedenen Betriebssystemplattformen. Die Methoden des os-Moduls, die sich auf Zugriffsrechte beziehen, sind eher auf die Unix-Plattform zugeschnitten, funktionieren aber auch eingeschränkt unter Windows: Methode
Erläuterung
access(pfad, modus)
Gibt True zurück, wenn der aktuelle Nutzer für die unter pfad angegebene Datei das für modus angegebene Recht hat. Für modus wird eine Ganzzahl oder eine Konstante des os-Moduls verwendet: 0 (F_OK) – Datei ist vorhanden 1 (X_OK) – Datei ist ausführbar 2 (W_OK) – Datei ist beschreibbar 4 (R_OK) – Datei ist lesbar
chmod(pfad, modus)
Ändert das Zugriffsrecht der unter pfad angegebenen Datei auf modus.
Tabelle 7.7: Methoden zum Abfragen und Setzen von Zugriffsrechten
170
7.2 Das Modul os
Der folgende Aufruf der Methode access() testet, ob Schreibrecht für die Datei existiert: >>> os.access(r'c:\daten\adressen.txt',2) False
Unter Windows gibt die Methode dann False aus, wenn in den Dateieigenschaften der Schreibschutz aktiviert wurde. Unter Unix ist die Ausgabe davon abhängig, welche Rechte explizit für die Datei gesetzt wurden. Mit chmod() kann dies im Unix-Stil mit Oktalzahlen gemacht werden: os.chmod(r'c:\daten\adressen.txt',444)
Das Beispiel stellt für die Datei adressen.txt Leserecht für alle, also Besitzer, Gruppe und andere, ein. Unter Windows kann so der Schreibschutz mit Python eingestellt werden. Für Unix-Unkundige zeigt die folgende Tabelle, wie die Rechte über Oktalzahlen vergeben werden: Besitzer
Gruppe
Andere
Lesen
400
040
004
Schreiben
200
020
002
Ausführen
100
010
001
Tabelle 7.8: Matrix der Rechte unter Unix Um z. B. Lese- und Schreibrechte zu vergeben, werden die jeweiligen Zahlen addiert. Für die Gruppe ergibt sich so der Wert 060. Soll auch der Besitzer Lese- und Schreibrecht haben, so werden quasi 600 dazuaddiert, was den Wert 660 ergibt. Ein typischer Wert ist z. B. 644. Hier kann der Benutzer lesen und schreiben, andere, auch aus seiner Gruppe, dürfen jedoch nur lesen. Mit einer vierten Stelle, die aber am Anfang der Zahlenreihe notiert wird, können SUID, SGID und Sticky-Bit gesetzt werden. Für Windows haben sie keine Bedeutung.
7.2.5
Verzeichnisbäume durchlaufen
Wie viele Dateien sind in einem Verzeichnis und all seinen Unterverzeichnissen enthalten? Wie viel Speicherplatz belegt das Verzeichnis mit allen enthaltenen Dateien auf der Festplatte? Wenn man solche Informationen ermitteln will, muss man durch alle Unterverzeichnisse gehen und die entsprechenden Informationen zusammensammeln. Das kann je nach Verzeichnistiefe eine langwierige Aufgabe sein. Aber das Untermodul path stellt eine Funktion zur Verfügung, mit der eine solche Aufgabe zum Kinderspiel wird. Es ist die Methode walk(), die drei Parameter benötigt:
171
7 Arbeiten mit dem Laufzeit- und dem Betriebssystem
Den Pfad des Verzeichnisses, ab dem der Durchlauf beginnen soll Den Namen einer Prozedur, die die Arbeit in den Verzeichnissen erledigt Den Namen einer Variablen, die die gesammelten Informationen aufnimmt
Die Prozedur, die die Informationen sammelt, ist quasi die Arbeitsbiene in dem Prozess. Sie muss ebenfalls drei Parameter haben: den Namen der Variablen für die gesammelten Informationen, einen Parameter für das aktuelle Verzeichnis sowie einen Namen für die Dateiliste dieses Verzeichnisses. Die Methode walk() übernimmt in dem Prozess eine übergeordnete Funktion. Es reicht, an die Prozedur den aktuellen Stand der Informationssammlung, den Pfad des aktuellen Verzeichnisses sowie die Dateiliste dieses Verzeichnisses zu übergeben. Mit einem Beispiel soll die Funktionsweise erläutert werden. Das folgende Skript ermittelt die Anzahl von Dateien und Verzeichnissen in einem Verzeichnisbaum und errechnet die Gesamtgröße aller Dateien: Listing 7.4: Ermittlung von Dateianzahl und Speicherbedarf mit der Methode walk( ) 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
import os def getDirInfos(infos,verz,dats): # aktuelles Arbeitsverzeichnis bestimmen os.chdir(verz) for dat in dats: # Prüfen, ob Datei vorliegt if os.path.isfile(dat): # Dateigröße zur Gesamtgröße addieren infos['size'] += os.path.getsize(dat)/1024.0 # Dateianzahl um den Wert 1 erhöhen infos['dat'] += 1 else: # Verzeichnisanzahl um den Wert 1 erhöhen infos['verz'] += 1 # Ablauf infos = {'verz':0,'dat':0,'size':0} os.path.walk(r'c:\python25',getDirInfos,infos)
Der Aufruf von walk() erfolgt in Zeile 19. Für den Test wurde das Verzeichnis der Python-Installation angegeben. Die Arbeitsprozedur ist getDirInfos(), die ab Zeile 03 definiert ist. Als dritter Parameter wird ein Dictionary übergeben, das in Zeile 18 mit Anfangswerten für Verzeichnisse, Dateien und Speichergröße definiert wurde. In der Arbeitsprozedur wird jeweils das aktuelle Arbeitsverzeichnis geändert (Zeile 05). Das ermöglicht es, direkt mit den Dateinamen zu arbeiten. Es muss nun nur noch nach Dateien und Verzeichnissen unterschieden gezählt werden und die Dateigrößen müssen addiert werden (Zeile 08–15). Ist der Durchlauf abgeschlossen, wird das Ergebnis ausgegeben (Zeile 22–26). Ein Skriptaufruf bewirkt folgende Ausgabe: Ergebnis: 64 Verzeichnisse 2009 Dateien Kilobytes gesamt: 33530.88
173
8 Grafische Benutzeroberflächen Alle Benutzereingaben, die man in den bisherigen Beispielen machen konnte, waren textbasiert und ließen sich folglich nur über die Tastatur vornehmen. Dabei war immer nur eine Eingabe zu einem Zeitpunkt möglich. Erst wenn die eine Eingabe gemacht wurde, erschien eventuell eine weitere. Der Ablauf der Eingaben wird dabei vom Programm vorgegeben. Das kann in manchen Situationen sicherlich Vorteile haben, entspricht aber nicht mehr dem Benutzerverhalten. Dieser ist es gewohnt – auch wenn ihm das selbst möglicherweise gar nicht immer bewusst ist –, viel komplexere Strukturen zu bedienen, als es noch zu Zeiten von MS-DOS der Fall war. Möglich ist dies durch grafische Oberflächen, die eine Vielzahl von Eingabe- und Auswahlmöglichkeiten parallel zur Verfügung stellen, die vom Anwender mit der Tastatur, vor allem aber auch mit der Maus bedient werden können. Es sind damit deutlich komplexere Programmstrukturen möglich, aber auch notwendig. Wenn man als Entwickler nicht weiß, was der Anwender zu einem Zeitpunkt X auf der Programmoberfläche macht, muss man das Programm so entwickeln, dass alle angebotenen Features sinnvoll funktionieren, oder wenigstens eine Rückmeldung geben, wenn eines nicht verfügbar ist. Natürlich kann man eine Programmoberfläche sehr schnell mit Buttons und Auswahlkästchen überladen und dem Benutzer jegliche Orientierung nehmen. Mittlerweile werden Programme aber meistens so konzipiert, dass nur die Elemente angezeigt werden, die im jeweiligen Arbeitskontext tatsächlich benötigt werden. Darüber hinaus kann der Nutzer die Oberfläche in der Regel seinen Bedürfnissen anpassen, so dass eine individuelle Arbeitsumgebung entsteht. Und schließlich darf man nicht vergessen, dass die Usability von grafischen Oberflächen in sehr starkem Maße von ihrer grafischen Gestaltung abhängig ist. Hier sind in der Regel professionelle InterfaceDesigner am Werk, die mit vielen Usability-Tests eine optimale Oberfläche entwickeln. Man kann auf jeden Fall erahnen, dass Programme mit grafischer Benutzeroberfläche deutlich an Komplexität zunehmen. Nicht allein, dass die Programmierung der Oberfläche hinzukommt, auch die reinen Funktionalitäten müssen aufgrund der Paralleli-
8 Grafische Benutzeroberflächen
tät der Features anders konzipiert werden. Es würde den Rahmen dieses Buchs sprengen, auf alle Aspekte der Entwicklung von grafischen Oberflächen einzugehen. Hier wird es darum gehen, einen Überblick über die Möglichkeiten zu geben, die das Modul Tkinter bietet, das zur Standardinstallation von Python gehört. Tkinter ist eine Art Baukasten, der eine Vielzahl von Elementen (Buttons, Eingabefelder etc.) enthält, die man für grafische Oberflächen benötigt. Es ist kein reines Python-Modul, sondern eine Python-Schnittstelle zu Tcl/Tk (http://tcl.sourceforge.net/). Es ist aber quasi der Standard für grafische Oberflächen mit Python. Auch die Oberfläche der IDLE ist mit Tkinter realisiert. Hinweis Tkinter ist nicht das einzige Modul, mit dem sich grafische Oberflächen in Python entwickeln lassen. Daneben existieren noch: wxPython (http://www.wxpython.org) – ein Python-Modul, das die Schnittstelle zu den in C++ implementierten wxWidgets bildet. PyGTK (http://www.pygtk.org) – ein Python-Modul, das die GTK+Bibliothek in Python verfügbar macht. GTK wird vornehmlich zur Entwicklung von Programmen für die Gnome verwendet, kann aber auf allen anderen Plattformen ebenfalls eingesetzt werden. PyQt (http://www.riverbankcomputing.co.uk/pyqt/index.php) – dieses Modul bindet die Qt-Bibliotheken in Python ein. Es lassen sich damit Oberflächen für alle Plattformen entwickeln. In ihrer Anlage sind sie Tkinter ähnlich. Da sie aber nicht zur Standardinstallation gehören, müssen sie separat hinzuinstalliert werden.
8.1
Grundlagen
Die Grundidee bei der Arbeit mit Tkinter ist, dass Oberflächenelemente in ein Fensterobjekt platziert werden. Die oberste Referenz, quasi die Klammer, die alles zusammenhält, ist also ein Programmfenster. Es stehen eine Reihe von Elementen zur Verfügung, die man je nach Bedarf in das Fenster packt. Diese Elemente werden bei Tkinter Widgets (zu Deutsch etwa: Vorrichtung) genannt. Interaktivität wird durch Funktionen realisiert, die mit den Widgets verbunden werden. Ein erstes einfaches Beispiel zeigt die Vorgehensweise: Listing 8.1: Erstes Fenster mit Interaktivität 01 def ausgabe(): 02 """ Button-Funktion""" 03 fenster.label.config(text='Hallo Kleiner') 04
Hier sind alle Grundelemente bereits versammelt, die für eine grafische Oberfläche notwendig sind. Zunächst ist das der Import aller Namen des Tkinter-Moduls (Zeile 05). Dann wird das Fensterobjekt von der Klasse Tk gebildet (Zeile 07). In dieses Fenster werden dann zwei Widgets eingefügt. Widgets werden als Instanzen der entsprechenden Klasse gebildet. Das erste Widget ist ein Label, ein Beschriftungsfeld, das von der Klasse Label instanziert wird (Zeile 09). Dem Konstruktor muss das Fensterobjekt als Argument übergeben werden und zusätzlich erhält es mit dem Argument text einen Beschriftungstext. Das zweite Widget wird von der Klasse Button abgeleitet und erzeugt eine Schaltfläche (Zeile 12). Auch hier erfolgt wieder die Übergabe des Fensterobjekts. Als drittes Argument wird für command der Name einer Funktion (ausgabe) übergeben. Diese Funktion wird ausgeführt, wenn man die Schaltfläche betätigt. Die beiden Widgets werden als neue Attribute des Fensterobjekts definiert. Dies ist notwendig, damit die Widgets miteinander interagieren können. Damit die beiden Widgets im Fenster dargestellt werden, muss ein sogenannter Layoutmanager aufgerufen werden. Dies geschieht hier mit der Methode pack() (Zeilen 10 und 14). Damit das Fenster dauerhaft geöffnet bleibt wird die Methode mainloop() aufgerufen (Zeile 16). Die eigentliche Erstellung der Oberfläche ist abgeschlossen. Es fehlt aber noch die Funktion, die durch den Klick auf den Button ausgelöst werden soll. Sie ändert mit der Methode config() den Beschriftungstext des Labels (Zeile 03). Dazu muss lediglich für das Argument text ein neuer Wert angegeben werden. Die Funktion muss vor dem Button, der sie aufruft, definiert werden, weil bei der Erstellung des Buttons geprüft wird, ob die beim Argument command angegebene Funktion existiert. Ist dies nicht der Fall, kommt es zu einem Fehler. Führt man dieses Skript nun aus, erhält man ein einfaches Fenster mit den beiden Oberflächenelementen:
177
8 Grafische Benutzeroberflächen
Abbildung 8.1:
Das erste Fenster
Nach dem Klick auf die Schaltfläche ändert sich dann die Schaltfläche und das Fenster sieht folgendermaßen aus:
Abbildung 8.2: Das Fenster nach dem Klick Mehr geht im Moment noch nicht. Das heißt, ganz korrekt ist das nicht. Denn das Fensterobjekt selbst bringt schon die Grundfunktionalität jedes Fensters mit. Es gibt ein Fenstermenü, das durch Klick auf das rote Tk-Icon aufklappt. Es gibt ebenfalls die Buttons zum Minimieren, Maximieren und Schließen des Fensters. Und schließlich kann man das Fenster verschieben und durch Anfassen der Ränder vergrößern (siehe Abbildung 8.3).
178
8.1 Grundlagen
Abbildung 8.3: Das Fenster vergrößern Das Fensterobjekt stellt also auch die Verbindung zum Betriebssystem her und nutzt dessen Fenstermanagement. Das Beispiel zeigt, dass der Aufbau der Oberfläche (Layout) von den Funktionen (im Beispiel natürlich nur eine) getrennt sind. Es gibt bei der Verwendung von Tkinter keine Vermischung dieser beiden Ebenen. Lediglich die Funktionsaufrufe bilden Schnittstellen zwischen den Ebenen. Deutlicher wird dies, wenn man das Beispiel objektorientiert aufbaut. Gleichzeitig schafft man damit eine deutlich effektivere Programmierung: Listing 8.2: Erstes Fenster objektorientiert 01 from Tkinter import * 02 03 class Fenster(): 04 def __init__(self): 05 # Hauptfenster erstellen 06 master = Tk() 07 # LAbel erzeugen 08 self.label = Label(master,text='Hello World') 09 self.label.pack() 10 # Button erzeugen 11 self.button = Button(master, text="Klick mich", 12 command=self.__ausgabe) 13 self.button.pack() 14 # Fenster anzeigen 15 master.mainloop()
179
8 Grafische Benutzeroberflächen
16 17 18 19
def __ausgabe(self): """ Methode für den Button """ self.label.config(text='Hallo Kleiner')
Hier wird das Fenster über die Konstruktormethode gebildet. Die Widgets sind nun nicht mehr Attribute des Fensterobjekts, sondern der Instanz der neuen Klasse. Aus der Funktion, die hinter dem Button liegt, ist eine Methode geworden. Diese kann als private Methode definiert werden, weil sie nur von »innen«, vom Button-Widget aufgerufen werden muss und soll. Mit der Bildung einer Instanz von dieser Klasse wird das Programmfenster direkt erstellt.: >>> app = Fenster()
Schreibt man diese Zeile an das Ende des Skripts, kann man unter Windows das Programmfenster durch Doppelklick auf die Skriptdatei erhalten. Es öffnet sich allerdings auch die Eingabeaufforderung. Das kann für Testzwecke sehr hilfreich sein, da dort Fehlermeldungen ausgegeben werden. Will man die Eingabeaufforderung aber unterdrücken, muss man der Skriptdatei die Endung .pyw geben. Unter Linux benötigt das Skript dafür noch die Magic Line (siehe Abschnitt 2.2.2) und die Rechte müssen so eingestellt werden, dass das Skript ausführbar wird.
8.2
Oberflächenelemente: Widgets
Die verschiedenen Widgets, die für der Erstellung von Oberflächenelementen zur Verfügung stehen, sind allesamt Klassen des Tkinter-Moduls. Die folgende Tabelle listet die wichtigsten mit ihrem jeweiligen Klassennamen auf: Klassenname
Oberflächenelement
Button
Schaltfläche
Canvas
Grafisches Element
Checkbutton
Checkbox zum Ankreuzen
Entry
Eingabefeld
Frame
Ein Rahmen, der unsichtbar sein kann (dient zur Gruppierung von anderen Elementen)
Label
Beschriftung
LabelFrame
Ein sichtbarer Rahmen mit Beschriftung (dient zur Gruppierung von anderen Elementen)
Listbox
Auswahlliste
Tabelle 8.1: Klassen für das Oberflächenlayout
180
8.2 Oberflächenelemente: Widgets
Klassenname
Oberflächenelement
Menu
Menü, das weitere Menüs enthalten kann
Menubutton
Pull-down-Menü
Message
Mehrzeilige Beschriftung oder Textausgabe
OptionMenu
Optionsliste (als Pull-down-Menü)
PanedWindow
Eine Fensterunterteilung (dient zur Gruppierung und Anordnung anderer Elemente)
PhotoImage
Bild
Radiobutton
Radiobutton
Scale
Schieberegler
Scrollbar
Scrollbar
Spinbox
Eingabefeld mit hinterlegter Optionsliste (ermöglicht Eingabe von Werten, aber auch das Durchscrollen von festgelegten Werten)
Text
Mehrzeilige Texteingabe
Tk
Fenster (Hauptfenster)
Toplevel
Fenster (abhängiges Fenster)
Tabelle 8.1: Klassen für das Oberflächenlayout (Forts.) Die Erstellung der Widgets erfolgt durch die Instanzierung der entsprechenden Klasse. In der Regel wird bei der Instanzierung ein sogenannter Master übergeben. Dies ist entweder die Instanz des Hauptfensters, eines Frame oder einer Fensterunterteilung. Andere Widgets sind nicht in der Lage, Elemente aufzunehmen. Auf diese Weise werden Widgets logisch angeordnet und miteinander in Beziehung gesetzt. Mit weiteren Parametern können die Widgets konfiguriert und in ihrer Erscheinung angepasst werden. Allgemein sieht die Erstellung eines Widget folgendermaßen aus: widget = Klasse(master,[optionen])
Mit dem Instanzieren allerdings erscheint das Element noch nicht auf der Oberfläche. Für die Darstellung und Anordnung sind sogenannte Layoutmanager zuständig. Dieser muss für das Widget explizit aufgerufen werden. Allgemein: widget.layoutmanager()
Dadurch wird das Widget auch grafisch im Bereich seines Masters angezeigt. Standardfunktionen der Elemente, wie z. B. Kopieren und Einfügen in Eingabefelder oder die Cursorkontrolle über die Maus oder die Pfeiltasten werden von den Widget-Klassen bereits geliefert. Weitergehende Funktionalität wird über eigene Funktionen hinzugefügt.
181
8 Grafische Benutzeroberflächen
8.2.1
Allgemeine Widget-Optionen
Einige Optionen, die bei der Instanzierung der Widgets eingestellt werden können, sind für die meisten Widgets identisch. Die folgende Tabelle listet sie auf: Option
Erläuterung
foreground, fg
Vordergrundfarbe
background, bg
Hintergrundfarbe
width
Breite in Pixel
height
Höhe in Pixel
borderwidth, bd
Breite eines Rahmens in Pixel
relief
Rahmenform, wird über Konstanten angegeben: FLAT, GROOVE, RAISED, RIDGE, SUNKEN
padx
Innenabstand links und rechts in Pixel
pady
Innenabstand oben und unten in Pixel
text
Zeichenkette für die Beschriftung
textvariable
Variable Beschriftung (Referenz auf ein StringVar-Objekt)
font
Schriftangabe (Tupel mit den drei Elementen Schriftart, Größe, Schriftstil)
justify
Ausrichtung der Beschriftung, wird über Konstanten angegeben: CENTER, LEFT, RIGHT
underline
Ganzzahl, die die Nummer des Zeichens der Beschriftung angibt, das unterstrichen werden soll. Der Standardwert –1 steht für keine Unterstreichung.
cursor
Name eines Cursors, der verwendet wird, wenn die Maus sich über dem Widget befindet. Für Cursornamen siehe: http://infohost.nmt.edu/tcc/help/pubs/tkinter/cursors.html
command
Name einer Funktion oder Methode, die aufgerufen wird, wenn das Widget betätigt wird
state
Bestimmt den Status. Wird über Konstanten definiert: ACTIVE, DISABLED, NORMAL
Tabelle 8.2: Optionen, die für die meisten Widgets gelten Die Konfiguration eines Widget erfolgt bei der Instanzierung seiner Klasse, z. B.: but = Button(master, text='Absenden', relief=RAISED, bd=2)
Nach der Erstellung des Widget kann mit der Methode config() die Konfiguration geändert werden, z. B.: but.config(state=DISABLED)
182
8.2 Oberflächenelemente: Widgets
Das nachträgliche Ändern des Aussehens eines Widget ist eine wichtige Möglichkeit, um Oberflächen zu erstellen, die eine gute Benutzerführung gewährleisten. Elemente, die gerade nicht benötigt werden, sollten deaktiviert werden, vorgenommene Einstellungen müssen beim Wiederaufruf eines Fensters angezeigt werden. Hier findet config() sein Einsatzgebiet. Manchmal ist es zum Zeitpunkt der Erstellung eines Widget nicht möglich, eine Option einzustellen, weil sich diese auf ein später erst zu erstellendes Objekt bezieht. Auch hier ist dann config() von Nutzen, um nach der Erstellung des Objekts dem Widget die Option hinzuzufügen. Es existiert zum Konfigurieren auch die Methode configure(), die genauso wie config() verwendet wird.
Farbangaben Farbangaben werden bei Tkinter hexadezimal gemacht, wie man es auch von Webseiten kennt. Entweder wird eine Farbtiefe von 4 Bit oder 8 Bit verwendet. Die Schreibweise ist allgemein #rgb (4 Bit) oder #rrggbb (8 Bit). Ein Beispiel: feld = Label(master, fg='#FFCC00', bg='#0000EE')
Grundfarben können auch über ihre englischen Namen zugewiesen werden: black, white, red, green, blue, cyan, magenta, yellow. Auch hier ein Beispiel: feld = Label(master, fg='yellow', bg='black')
Schriftangaben Die Auswahl, Größe und der Stil einer Schrift werden über ein Tupel angegeben, das drei Elemente haben kann: font=(Schriftart, Größe, [Stil])
Die Angabe für Stil kann auch weggelassen werden. In diesem Fall wird der Normalstil der angegebenen Schrift verwendet. Ein Beispiel: but = Button(master, text='Absenden' font=('Verdana', 12, 'italic'))
Für Schriftart sollte man eine wählen, von der auszugehen ist, dass sie auf dem System tatsächlich vorhanden ist. Andernfalls wird eine Standardschrift verwendet. Für die Stilangabe kann bold (fett), italic (kursiv), underline (unterstrichen) und overstrike (durchgestrichen) verwendet werden. Hier sind auch Kombinationen möglich: but = Button(master, text='OK' font=('Verdana', 12, 'bold italic'))
8.2.2
Allgemeine Widget-Methoden
Neben der bereits erwähnten Methode config() gibt es eine Reihe weiterer Methoden, die alle Widgets besitzen. In der Regel dienen sie der Bindung des Widget an einen Event-Handler, dem Aufruf einer Funktion oder dem Löschen des Widget.
183
8 Grafische Benutzeroberflächen
Die folgende Tabelle listet die Methoden auf: Methode
Erläuterung
after(ms, func, [args,...])
Zeitversetzter Aufruf einer Funktion nach ms Millisekunden. Gibt eine Id zurück.
after_cancel(id)
Abbruch des after-Aufrufs mit angegebener id
bind(sequence=event,func=f, [add='+'])
Bindet die Funktion f an den Event. Mit dem optionalen Parameter add='+' wird die Bindung zusätzlich zu bereits bestehenden hergestellt. Gibt eine Id zurück.
bind_all(sequence=event,func=f, [add='+'])
Wie bind(), aber die Bindung wird für alle Widgets hergestellt.
Wie bind(), aber die Bindung wird für alle Widgets, die auf der angegebenen klasse basieren, hergestellt.
unbind(sequence,funcid=id)
Alle Bindungen an den für sequence bezeichneten Event werden für das Widget aufgehoben. Wenn für funcid ein Wert übergeben wird, wird nur diese Bindung aufgehoben.
unbind_all(sequence)
Alle Bindungen an den für sequence bezeichneten Event werden aufgehoben.
unbind_class(klasse, sequence)
Alle Bindungen an den für sequence bezeichneten Event werden für alle Widgets aufgehoben, die auf der angegebenen Klasse basieren.
cget(option)
Gibt den Wert der angegebenen Option zurück.
config(option=wert, ...)
Ändert den Wert der angegebenen Option(en). Ohne Parameter liefern beide Methoden ein Dictionary mit der gesamten Konfiguration des Widget.
configure(option=wert, ...)
option_clear()
Alle Optionen werden auf die Standardwerte zurückgesetzt.
focus_get()
Gibt den Namen des Widget zurück, das aktuell den Focus hat.
Ein Objekt der Klasse Tk bildet das Hauptfenster einer Anwendung. Es ist immer auch der oberste Bezugsrahmen für andere Widgets. Dem Konstruktor kann ein Argument übergeben werden, das die Beschriftung der Titelzeile bestimmt: fenster = Tk(className='eigenes Fenster')
Ruft man den Konstruktor ohne Argument auf, bekommt das erzeugte Fenster den Titel Tk. Folgende Methoden hat das Fensterobjekt unter anderem: Methode
Erläuterung
title(Zeichenkette)
Ändert die Titelzeile des Fensters
geometry(BxH)
Bestimmt die Größe des Fensters. Übergeben wird eine Zeichenkette, z. B. 600x800.
iconbitmap(pfad)
Ändert das Icon in der Titelzeile. Dieses sollte maximal 16x16 Pixel groß sein.
Tabelle 8.4: Einige Methoden des Fensterobjekts
185
8 Grafische Benutzeroberflächen
Methode
Erläuterung
attributes(attr,...)
Bestimmt eine Reihe von Fenstereigenschaften vor allem unter Windows und MacOS. Die Übergabe der Parameter erfolgt in der Form '-attribut',wert. Folgende Attribute können eingestellt werden: – alpha – ein Wert zwischen 0.0 und 1.0, der die Transparenz des Fensters regelt – disabled – bestimmt, ob das Fenster deaktiviert ist (Wert 1) oder nicht (Wert 0). – toolwindow – wenn dieses Attribut gesetzt wird, erscheint das Fenster nur mit einem Schließen-Button in der Titelzeile. – topmost – wenn dieses Attribut gesetzt ist, wird das Fenster immer im Vordergrund gehalten.
iconify()
Minimiert das Fenster auf seine Titelzeile.
deiconify()
Zeigt das Fenster wieder komplett an, wenn vorher iconify() angewandt wurde.
resizable(width=v,height=v)
Bestimmt, ob die Größe des Fensters änderbar ist. Dies kann über die Parameter width und height getrennt für Breite und Höhe eingestellt werden. Der Standardwert ist jeweils 1.
withdraw()
Entfernt das Fenster vom Monitor. Mit deiconify() kann es wiederhergestellt werden.
Tabelle 8.4: Einige Methoden des Fensterobjekts (Forts.) Alle Methoden können auch mit dem Präfix wm_ aufgerufen werden, z. B. wm_withdraw(). Einen Unterschied in der Handhabung oder Ausführung gibt es aber nicht. Das folgende Beispiel erzeugt ein Fenster von 300x400 Pixel, mit einem neuen Icon und einer neuen Titelleistenbeschriftung: from Tkinter import * f = Tk() f.title('Daisy') f.geometry('300x400') f.wm_iconbitmap('daisy.ico') f.mainloop()
186
8.3 Die einzelnen Widget-Klassen
Abbildung 8.4: Fenster mit eigenem Text und Icon
8.3.2
Button
Der Konstruktor der Klasse Button erzeugt eine Schaltfläche, die mit der linken Maustaste betätigt wird, die Beschriftung wird mit der Option text gesetzt: b = Button(master, text='Klick hier')
Den optischen Effekt des Absenkens der Schaltfläche bringt das Button-Objekt bereits mit. Die eigentliche Funktion, die beim Anklicken ausgeführt wird, wird über die Option command zugewiesen: def ende():
# Beenden des Programms f.destroy() from Tkinter import * f = Tk() b = Button(f,text='Beenden', command=ende) b.pack() f.mainloop()
Das Beispiel erzeugt einen Button, der bei Klick das Programm beendet.
187
8 Grafische Benutzeroberflächen
Abbildung 8.5: Ein erzeugter Button Mit der Methode invoke() ist es möglich, die Funktion auszuführen, die bei der command-Option angegeben wurde, ohne dass der Button betätigt werden muss. Fehlt die command-Option, hat ein Aufruf von invoke() keine Auswirkungen.
8.3.3
Checkbutton
Mit der Klasse Checkbutton werden Ankreuzfelder oder Schaltflächen realisiert, die zwei Zustände haben können: »An« oder »Aus«. Die Erscheinungsform wird durch die Option indicatoron bestimmt.
Abbildung 8.6: Erscheinungsformen von Checkbuttons Neben den übergreifenden Optionen kann der Checkbutton noch über folgende weitere Optionen konfiguriert werden: Option
Erläuterung
indicatoron
Bestimmt die Erscheinungsform. Mit dem Standardwert 1 wird ein Ankreuzfeld erzeugt, mit dem Wert 0 eine Schaltfläche, die bei Klick eingedrückt bleibt.
offvalue
Eine Zahl oder eine Zeichenkette, die dem Zustand »Aus« zugeordnet wird. Der Standardwert ist 0.
Tabelle 8.5: Spezielle Optionen des Checkbuttons
188
8.3 Die einzelnen Widget-Klassen
Option
Erläuterung
onvalue
Eine Zahl oder eine Zeichenkette, die dem Zustand »An« zugeordnet wird. Der Standardwert ist 1.
selectcolor
Farbe des Checkbuttons, wenn er im Zustand »An« ist.
variable
Der Name der Kontrollvariablen
width
Bestimmt eine feste Anzahl von Zeichen für die Button-Aufschrift.
Tabelle 8.5: Spezielle Optionen des Checkbuttons (Forts.) Mit den folgenden Methoden können Checkbuttons gesteuert werden: Methode
Erläuterung
select()
In den Zustand »An« wechseln
deselect()
In den Zustand »Aus« wechseln
toggle()
Den Zustand wechseln
invoke()
Führt die Funktion aus, die bei der Option command angegeben wurde.
Tabelle 8.6: Wichtige Methoden des Checkbuttons Das folgende Beispiel zeigt einen Checkbutton, der eine (imaginäre) Maschine startet und wieder beendet: Listing 8.3: Skript für einen Checkbutton als Schalter 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19
from Tkinter import * f = Tk() #Fenstergröße bestimmen f.geometry('200x70') # Kontrollvariable erzeugen status = StringVar(f) # Anfangswert setzen status.set('Aus') def an_aus(): """ Funktion für Checkbutton """ if status.get() == 'An': l.config(text='Maschine an') else: l.config(text='Maschine aus') l = Label(f,text='Maschine aus',font=('Verdana',18,'bold')) l.pack() # Chekbutton erzeugen
189
8 Grafische Benutzeroberflächen
20 c = Checkbutton(f,indicatoron=0, 21 onvalue='An', # Wert, wenn gewählt 22 offvalue='Aus', # Wert, wenn nicht gewählt 23 variable=status, 24 command=an_aus, 25 textvariable=status, 26 width=3) 27 c.pack() 28 f.mainloop()
Für das Funktionieren ist hier die Kontrollvariable status von entscheidender Bedeutung (Zeile 06). Sie erhält als Wert jeweils die in den Optionen onvalue und offvalue (Zeile 21 und 22) definierten Werte. Damit wird in der Funktion gearbeitet (Zeile 10–15) und darüber wird auch die Beschriftung des Buttons verändert (Zeile 25). Das Ergebnis zeigt die folgende Abbildung:
Abbildung 8.7: Zwei Zustände des Checkbuttons
8.3.4
Entry
Für die Erstellung eines Eingabefelds wird die Klasse Entry verwendet. Der Benutzer kann in das Feld Text eingeben, der vom Programm ausgelesen wird. Es kann aber auch vom Programm Text in das Feld eingefügt werden. Ein Objekt der Klasse Entry hat folgende spezielle Optionen: Option
Erläuterung
show
Für diese Option kann ein einzelnes Zeichen angegeben werden, das anstelle der tatsächlichen Eingabe angezeigt wird.
width
Breite des Felds. Angegeben wird die Anzahl der sichtbaren Zeichen.
Tabelle 8.7: Spezielle Optionen für Eingabefelder
190
8.3 Die einzelnen Widget-Klassen
Folgende Methoden stehen u. a. für Entry-Objekte zur Verfügung: Methode
Erläuterung
insert(index, text)
Fügt Text ab der Position index ein.
delete(first,[last])
Löscht an der angegebenen Position ein Zeichen. Wird der zweite Parameter verwendet, werden alle Zeichen von first bis last gelöscht.
get()
Gibt den Inhalt des Felds zurück.
icursor(index)
Setzt den Cursor an die Position index.
index(index)
Verschiebt den Inhalt des Felds so, dass das Zeichen an der Position index am linken Rand des Felds steht. Die Methode hat nur dann eine Auswirkung, wenn es einen Textüberlauf gibt.
Tabelle 8.8: Methoden des Entry-Objekts Das folgende Beispiel erzeugt die beiden Erscheinungsformen des Eingabefelds: from Tkinter import * f = Tk() e1 = Entry(f, width=10) e1.pack() e2 = Entry(f, width=10,show='+') e2.pack() f.mainloop()
Abbildung 8.8: Zwei Entry-Felder
8.3.5
Label
Anders als das Entry-Feld kann der Inhalt eines Labels vom Benutzer nicht geändert werden. Ein Label ist geeignet für Beschriftungen von anderen Widgets oder als Ausgabefeld für (auch mehrzeilige) Hinweise auf der Programmoberfläche. Mit folgenden Optionen kann das Label modifiziert werden: Option
Erläuterung
width
Breite des Felds. Angegeben wird die Anzahl der sichtbaren Zeichen.
wraplength
Regelt den Zeilenumbruch. Man gibt eine Anzahl von Zeichen an, nach denen der Umbruch erfolgen soll. Mit dem Standardwert 0 wird bei tatsächlichen Zeilenumbrüchen eine neue Zeile begonnen.
Tabelle 8.9: Optionen des Label-Objekts
191
8 Grafische Benutzeroberflächen
Das Beispiel erzeugt ein Label mit mehrzeiligem Text: from Tkinter import * f = Tk() text="""sollte es wahr sein""" l = Label(f, text=text,justify=LEFT) l.pack() f.mainloop()
Abbildung 8.9: Ein Fenster mit einem Label
8.3.6
Listbox
Mit der Klasse Listbox kann eine Auswahlliste erzeugt werden, in der Werte zeilenweise eingetragen sind. Der Benutzer kann einen oder mehrere Werte auswählen. Die zusätzlichen Optionen für den Konstruktor sind: Option
Erläuterung
Selectbackground
Hintergrundfarbe für die ausgewählte Zeile
Selectforground
Schriftfarbe für die ausgewählte Zeile
Selectborderwidth
Rahmenbreite für die ausgewählte Zeile
Selectmode
Darüber wird definiert, wie die Auswahl erfolgen kann. Folgende Konstanten können verwendet werden: BROWSE – eine Zeile kann gewählt werden. Bei gedrückter Maustaste folgt die Auswahl mit dem Cursor. MULTIPLE – mehrere Zeilen können durch Klicken ausgewählt werden. EXTENDED – mehrere untereinanderliegende Zeilen können durch Ziehen der Maus ausgewählt werden. SINGLE – eine Zeile kann ausgewählt werden.
Tabelle 8.10: Optionen für die Listbox-Klasse Die einzelnen Zeilen in einer Listbox sind indexiert wie in einer Liste. Mit folgenden Methoden hat man Zugriff auf die Werte:
192
8.3 Die einzelnen Widget-Klassen
Methode
Erläuterung
insert(index, zeile[n])
Eine oder mehrere Zeilen werden ab der Position index eingefügt.
size()
Gibt die Anzahl der Zeilen der Listbox zurück.
activate(index)
Wählt die Zeile an Position index aus.
curselection()
Gibt einen Tupel mit den Indexnummern der ausgewählten Zeilen zurück.
get(first,[last])
Gibt den Inhalt der Zeile mit dem Index first zurück. Wenn der zweite Parameter verwendet wird, werden die Zeilen von first bis last zurückgegeben.
delete(first,[last])
Löscht die Zeile mit dem Index first. Wenn der zweite Parameter verwendet wird, werden die Zeilen von first bis last gelöscht.
Tabelle 8.11: Methoden des Listbox-Objekts Das Beispiel erzeugt eine Liste zur Auswahl einer Programmiersprache: from Tkinter import * f = Tk() l = Listbox(f,selectmode=BROWSE) l.pack() for sprache in ['C++','Java','Python']: l.insert(END,sprache) f.mainloop()
Abbildung 8.10: Eine Auswahlliste zur Sprachauswahl
8.3.7
Menu
Die Klasse Menu dient dem Aufbau eines Menüs, wie es viele Programme unterhalb der Titelleiste haben. Dabei wird die Menüleiste über die Klasse Menu erstellt, aber auch die einzelnen Menüs, die beim Anklicken eines Punkts in der Menüleiste aufklappen. Hat ein Menü ein Untermenü, wird auch dieses über die Klasse Menu realisiert. Ein Gesamtmenü entseht durch die Verschachtelung einzelner Menüs. Einzelne Menüpunkte können einfache Befehle, Check- oder Radiobuttons sein. Die beiden letztgenannten erhalten ein Häkchen vor ihrer Beschriftung, wenn sie ausgewählt sind.
193
8 Grafische Benutzeroberflächen
Eine Menüleiste wird erzeugt, wenn man dem Hauptfenster ein Menu-Objekt über die Option menu zuweist. Ein Pull-down-Menü für einen Punkt in der Menüleiste wird mit der Menümethode add_cascade hinzugefügt: Listing 8.4: Erstellung einer Menüleiste mit einem Menüpunkt 01 02 03 04 05 06 07
from Tkinter import * f = Tk() mle = Menu(master=f)
Es werden zwei Menüobjekte erstellt (Zeile 03 und 05). Das eine, das die Menüleiste wird, erhält als Master das Hauptfenster. Damit dieses die Leiste darstellen kann, muss ihm das Menü mit der Methode config() zugewiesen werden (Zeile 04). Dem anderen Menü wird das erste Menü als Master zugewiesen. Mit der Methode add_cascade() wird dann der Menüleiste das zweite Menü hinzugefügt (Zeile 06). Mit der Option label wird der Name des Menüpunkts festgelegt. Die Option menu setzt die Referenz auf das zweite Menü. Es gibt also von Seiten des Menüs eine Referenz auf die Menüleiste und umgekehrt. Das Ergebnis zeigt die folgende Abbildung:
Abbildung 8.11: Menüleiste mit einem Menüpunkt Das Menü »Datei« hat noch keine Menüpunkte. Diese können mit den folgenden Methoden hinzugefügt werden: Methode
Erläuterung
add_command(option=wert,…)
Fügt dem Menü einen einfachen Befehl hinzu. Der Name des Menüpunkts wird mit Option label festgelegt, mit der Option command wird eine Funktion zugewiesen, die bei Auswahl des Menüpunkts ausgeführt wird.
add_checkbutton(option=wert,…)
Fügt dem Menü einen Checkbutton hinzu. In der Konfiguration ist er wie ein Checkbutton-Widget zu behandeln (Abschnitt ).
Tabelle 8.12: Methoden zum Hinzufügen von Menüpunkten
194
8.3 Die einzelnen Widget-Klassen
Methode
Erläuterung
add_radiobutton(option=wert,…)
Fügt dem Menü einen Radiobutton hinzu. In der Konfiguration ist er wie ein Radiobutton-Widget zu behandeln (Abschnitt ).
add_cascade(option=wert,…)
Fügt dem Menü ein Untermenü hinzu. Mit der Option menu wird die Referenz auf das zugehörige Menüobjekt hergestellt.
add_separator()
Fügt dem Menü eine Trennlinie hinzu
insert_command(index, option=wert,…)
Wie add_command(), aber an der mit index bezeichneten Position im Menü
insert_checkbutton(index, option=wert,…)
Wie add_checkbutton(), aber an der mit index bezeichneten Position im Menü
insert_radiobutton(index, option=wert,…)
Wie add_radiobutton(), aber an der mit index bezeichneten Position im Menü
insert_cascade(index, option=wert,…)
Wie add_cascade(), aber an der mit index bezeichneten Position im Menü
add_separator(index)
Wie add_separator(), aber an der mit index bezeichneten Position im Menü
Tabelle 8.12: Methoden zum Hinzufügen von Menüpunkten (Forts.) Die folgende Erweiterung des obigen Beispiels fügt dem Menü DATEI einige Menüpunkte hinzu: Listing 8.5: Hinzufügen von Menüpunkten from Tkinter import * def test(): """ Dummy-Funktion für Menüpunkte """ pass f = Tk() # Menüleiste erstellen mle = Menu(master=f) f.config(menu=mle) # Menu erstellen m1 = Menu(master=mle) # Menüpunkte erstellen m1.add_command(label='Neu', command=test) m1.add_command(label='Oeffnen', command=test) m1.add_separator() m1.add_command(label='Speichern', command=test) m1.add_command(label='Speichern unter', command=test) # Menü der Menüleiste hinzufügen mle.add_cascade(label='Datei', menu=m1) f.mainloop()
195
8 Grafische Benutzeroberflächen
Die Option command verweist im Beispiel auf eine Dummy-Funktion, die nichts bewirkt. Die Methoden zum Hinzufügen von Menüpunkten, die mit add_ beginnen, fügen den neuen Menüpunkt am Ende des Menüs an, so dass ein Menü entsteht, dessen Punkte wohlgeordnet untereinander stehen. Verwendet man die Option columnbreak= 1, wird mit dem Menüpunkt, der die Option bekommt, eine neue Spalte im Menü begonnen. Alle folgenden Punkte werden in der neuen Spalte unterhalb dieses Menüpunkts eingefügt. Die folgende Abbildung zeigt das ausgeklappte Menü.
Abbildung 8.12: Ausgeklapptes Menü Um in das Dateimenü ein Untermenü einzufügen, benötigt man ein drittes Menüobjekt. Dieses wird dann als Untermenü benutzt: Listing 8.6: Ein Untermenü einfügen 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22
# Menüpunkt für Untermenü m2.add_command(label='Email', command=test) f.mainloop()
Für das neue Menü wird als Master das Dateimenü festgelegt (Zeile 23). Das so erzeugte Menüobjekt wird dann mit der Methode add_cascade() als Untermenü mit dem Namen Email eingefügt. Es ist notwendig, dass das Menüobjekt existiert, bevor die Methode add_cascade() ausgeführt wird, da es sonst zu einem Fehler kommt. Das Ergebnis dieser Erweiterung des Skripts zeigt die folgende Abbildung:
Abbildung 8.13: Menü mit Untermenü Neben den Methoden zum Hinzufügen von Menüpunkten gibt es noch einige weitere, mit denen man ein Menü und dessen Punkte steuern kann: Methode
Erläuterung
delete(start,[end])
Löscht den Menüpunkt an der Position start. Wird der zweite Parameter verwendet, werden die Menüpunkte von start bis end gelöscht.
invoke(index)
Führt die Funktion aus, die mit dem Menüpunkt an der Position index verbunden ist.
entrycget(index, option)
Gibt den Wert der angegebenen Option des Menüpunkts an der Position index zurück.
type(index)
Gibt den Typ des Menüpunkts an der Position index zurück.
Tabelle 8.13: Weitere Methoden des Menüobjekts
197
8 Grafische Benutzeroberflächen
8.3.8
Menubutton
Mit der Klasse Menubutton ist es möglich, ein Menü an einer beliebigen Position im Fenster zu platzieren. Sichtbar ist zunächst nur ein Menübutton. Das Menü klappt auf, wenn man diesen anklickt. Dabei lässt sich bestimmen, auf welcher Seite des Menübuttons das Menü aufklappt. Der Menübutton fungiert als Master für das Menü, das wie im vorigen Abschnitt gezeigt, erstellt wird. Mit der Methode config() wird dem Button das Menü zugewiesen. Ein Beispiel: Listing 8.7: Ein Menü im Fenster mit der Klasse Menubutton 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21
from Tkinter import * def test(): """ Dummy-Funktion für Menü """ pass f = Tk() f.geometry('160x120') # Fenstermenü erstellen mb = Menubutton(f, text="Im Fenster", relief=RAISED, direction='above') mb.pack(side=BOTTOM)
# Menü mit Menüpunkten erstellen m = Menu(master=mb) m.add_command(label='sizt', command=test) m.add_command(label='das Menue', command=test) # Menü dem Fenstermenü hinzufügen mb.config(menu=m) f.mainloop()
Zunächst wird der Menübutton erstellt und im Fenster platziert (Zeile 09-12). Dann wird das Menü erzeugt (Zeile 15–17) und schließlich dem Menubutton zugewiesen (Zeile 20). Die Option direction bestimmt, auf welcher Seite des Menübuttons das Menü aufklappt. Mögliche Werte sind left, right, above und below (Standardwert). Das Menü kann jede Art von Menüpunkt haben, auch Untermenüs sind möglich.
198
8.3 Die einzelnen Widget-Klassen
Abbildung 8.14: Ein Menü im Fenster
8.3.9
Message
Das Message-Widget dient dem Anzeigen von mehrzeiligem Text. Es ist dem Label sehr ähnlich, das häufiger zum Einsatz kommt. Tatsächlich wird es in vielen Referenzen nicht mehr erwähnt. Im Unterschied zum Label kann man aber beim Message-Widget die Abmessung über das Breiten-Höhen-Verhältnis bestimmen. Bei Fenstern, deren Inhalte sich bei Größenänderungen mit anpassen sollen, ist es deshalb besser einsetzbar als das Label. Hier ein Beispiel: Listing 8.8: Ein Message-Feld mit eingestelltem Seitenverhältnis. 01 02 03 04
from Tkinter import * f = Tk() f.geometry('160x120') text = """Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua."""
Das Seitenverhältnis wird mir der Option aspect eingestellt, dessen Standardwert 150 ist. Dies bedeutet, dass die Breite 150% der Höhe des Felds ausmacht. Ausgangsgröße ist also die Höhe des Felds. Bei einem Wert von 200 ist das Feld doppelt so breit wie hoch. Das folgende Bild zeigt die gleiche Textmenge in zwei verschieden eingestellten Seitenverhältnissen.
Abbildung 8.15: Message-Felder mit unterschiedlichen Seitenverhältnissen
199
8 Grafische Benutzeroberflächen
8.3.10
OptionMenu
Mit der Klasse OptionMenu wird eine Optionsliste erzeugt, die als Pull-down-Menü angezeigt wird. Der Konstruktor benötigt drei Parameter, damit die Instanzierung fehlerfrei läuft. Neben der Angabe des Masters sind dies der Name einer Kontrollvariablen, die die aktuell gewählte Option speichert, und eine Liste mit Werten der Optionen. Weitere Konfigurationsmöglichkeiten gibt es bei diesem Widget nicht. Das folgende Beispiel zeigt den Aufbau einer Optionsliste: Listing 8.9: Erzeugung einer Optionsliste 01 02 03 04 05 06 07 08 09 10 11 12 13
Die Werte für die Liste sind in der Sequenz sprachen abgelegt (Zeile 05). Anschließend wird die Kontrollvariable definiert und ihr ein Anfangswert zugewiesen (Zeile 07 und 09). Schließlich wird die Optionsliste erzeugt (Zeile 10). Die folgende Abbildung zeigt das Ergebnis:
Abbildung 8.16: Eine Optionsliste
8.3.11
PhotoImage
Einige Widgets können Bilder darstellen, etwa das Label oder der Button. Diese müssen aber in Instanzen von PhotoImage verpackt sein. Beim Aufruf des Konstruktors wird nur der Pfad zur Bilddatei übergeben. Es werden nur die Bildformate GIF und
200
8.3 Die einzelnen Widget-Klassen
PPM/PGM unterstützt. Das folgende Beispiel zeigt die Darstellung eines Bilds in einem Label: Listing 8.10: Ein Bildobjekt erzeugen 01 02 03 04 05 06 07
from Tkinter import * f = Tk() f.geometry('440x330') bild = PhotoImage(file='testbild.gif') l = Label(f, image=bild)
l.pack() f.mainloop()
Es ist darauf zu achten, dass der Pfad zur Bilddatei als Schlüsselwortargument übergeben wird. Im Beispiel befindet sich dieses im gleichen Ordner wie das Skript. Das Label erhält für die Option image den Namen des Bildobjekts.
Abbildung 8.17: Ein Label mit Bild
8.3.12
Radiobutton
Radiobuttons können wie Checkbuttons zwei Erscheinungsformen haben, als kreisförmige Anklickfelder oder als Buttons. Bestimmt wird dies auch hier durch die Option indicatoron. Neben den übergreifenden Optionen können für Radiobuttons noch folgende weitere Optionen konfiguriert werden:
201
8 Grafische Benutzeroberflächen
Abbildung 8.18: Erscheinungsformen von Radiobuttons Option
Erläuterung
Indicatoron
Bestimmt die Erscheinungsform. Mit dem Standardwert 1 wird ein Anklickfeld erzeugt, mit dem Wert 0 eine Schaltfläche, die bei Klick eingedrückt bleibt.
Value
Eine Zahl oder eine Zeichenkette, die an die Kontrollvariable übergeben wird, wenn der Button gewählt wurde
Variable
Der Name der Kontrollvariablen. Radiobuttons, die sich auf die gleiche Kontrollvariable beziehen, bilden eine Gruppe, von der nur ein Button ausgewählt sein kann.
Width
Bestimmt eine feste Anzahl von Zeichen für die Button-Aufschrift.
Tabelle 8.14: Spezielle Optionen des Radiobutton Mit den Methoden select() und deselect() kann ein Radiobutton aus- bzw. abgewählt werden. Das folgende Beispiel erzeugt eine Gruppe von Radiobuttons: Listing 8.11: Erzeugung einer Gruppe von Radiobuttons 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
from Tkinter import * f = Tk() f.geometry('160x60') # Kontrollvariable auswahl = StringVar(f) auswahl.set('python') # Radiobuttons erstellen r1 = Radiobutton(f,text='Java',value='java',variable=auswahl)
Durch den Anfangswert für die Kontrollvariable (Zeile 07) wird festgelegt, welcher der drei Radiobuttons beim Öffnen des Fensters ausgewählt ist.
202
8.3 Die einzelnen Widget-Klassen
Abbildung 8.19: Eine Gruppe von Radiobuttons
8.3.13
Scale
Mit der Klasse Scale wird ein Schieberegler erzeugt, mit dem Zahlenwerte eingestellt werden können. Über eine Reihe von Optionen können sowohl Aussehen als auch Verhalten des Widget bestimmt werden: Option
Erläuterung
orient
Bestimmt die Ausrichtung des Reglers. Mögliche Werte sind VERTICAL und HORIZONTAL.
showvalue
Bestimmt, ob neben dem Regler der aktuelle Zahlenwert gezeigt wird oder nicht.
tickinterval
Wenn diese Option gesetzt ist, erhält der Regler eine Skala mit Werten. Der Wert für die Option bestimmt das Intervall der Skala.
length
Bestimmt die Länge der Reglerschiene in Pixel, Standardwert ist 100.
sliderlength
Bestimmt die Länge des Reglers in Pixel. Standardwert ist 30.
width
Bestimmt die Breite der Reglerschiene in Pixel. Standartwert ist 15.
throughcolor
Bestimmt die Farbe der Reglerschiene.
from_
Legt die untere Grenze des Wertebereichs fest.
to
Legt die obere Grenze des Wertebereichs fest.
resolution
Bestimmt die Schrittweite der einstellbaren Werte. Standardwert ist 1.
variable
Name der Kontrollvariablen. Ihr Anfangswert bestimmt die Position des Reglers beim Öffnen des Fensters.
command
Name der Funktion, die bei jeder Bewegung des Reglers ausgeführt wird.
Tabelle 8.15: Optionen des Scale-Widget Mit den Methoden set() und get() ist es möglich, den aktuellen Wert des Widget zu setzen bzw. abzurufen. Das folgende Beispiel zeigt die stufenlose Skalierung eines Buchstabens mit einem Schieberegler:
from Tkinter import * f = Tk() f.geometry('240x180') def skalieren(event): l.config(font=('Verdana',sl.get(),'bold')) sl = Scale(f, orient=HORIZONTAL, showvalue=1, tickinterval=10, length=200, sliderlength=40, from_ = 10, to = 100, command = skalieren )
sl.pack() l = Label(f,text='A',font=('Verdana',10,'bold')) l.pack() f.mainloop()
Der Schieberegler wird mit einigen Optionen gebildet und mit der Funktion skalieren() verbunden (Zeile 08–16). Diese Funktion ermittelt den aktuellen Wert des Reglers mit der Methode get() und nutzt diesen für die Neukonfiguration des Labels (Zeile 06). Das Ergebnis des Skripts zeigt die folgende Abbildung.
Abbildung 8.20: Ein Schieberegler zum Skalieren eines Buchstabens
8.3.14
Scrollbar
Die Klasse Scrollbar erzeugt Rollbalken, mit denen die Inhalte z. B. in Textfeldern oder Listboxen verschoben werden können. Für sich allein machen Rollbalken keinen Sinn und so ist es notwendig, ein Scrollbar-Widget mit dem Widget zu verbinden,
204
8.3 Die einzelnen Widget-Klassen
dessen Inhalt verschiebbar sein soll. Das Aussehen und das Verhalten des Rollbalkens kann durch die folgenden Optionen eingestellt werden: Option
Methode
orient
Bestimmt die Ausrichtung des Rollbalkens. Mögliche Werte sind VERTICAL und HORIZONTAL.
jump
Bestimmt, ob die Funktion, die bei der Option command angegeben wird mit jeder Schiebebewegung ausgeführt wird (Wert 0, Standardwert) oder erst, wenn nach der Schiebebewegung die Maustaste losgelassen wird (Wert 1).
borderwidth
Bestimmt die Breite des Rands der Schiene.
throughcolor
Bestimmt die Farbe der Schiene.
command
Name der Funktion, die bei der Schiebebewegung ausgeführt wird.
Tabelle 8.16: Optionen des Scrollbar-Widget Die Verbindung zwischen Rollbalken und Inhalts-Widget erfolgt zweiseitig. Die command-Option des Scrollbar-Widget ruft eine Methode des Inhalts-Widget auf, die für das Verschieben ihres Inhalts zuständig ist. Dies kann entweder die Methode xview() für die horizontale Verschiebung oder yview() für die vertikale Verschiebung sein. Nur Text-, Listbox und Canvas-Widgets haben diese Methoden. Für das Inhalts-Widget wird entweder die Option xscrollcommand für die horizontale Verschiebung oder yscrollcommand für die vertikale Verschiebung gesetzt. Als Wert wird die Methode set() des Scrollbar-Widget angegeben. Das folgende Beispiel zeigt eine scrollbare Listbox: Listing 8.13: Scrollbalken für eine Listbox 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
from Tkinter import * f = Tk() sb = Scrollbar(f) sb.pack(side=RIGHT, fill=Y)
l = Listbox(f,selectmode=BROWSE, yscrollcommand=sb.set)
sprachen = ['Assembler', 'awk', 'Basic', 'C', 'C++', 'C#', 'Delphi', 'Fortran', 'Java', 'Logo', 'Pascal', 'Perl', 'PHP', 'Prolog', 'Python','Ruby'] for sprache in sprachen: l.insert(END,sprache) l.pack()
205
8 Grafische Benutzeroberflächen
16 sb.config(command=l.yview) 17 18 f.mainloop()
Der Rollbalken wird über den Layoutmanager (siehe Abschnitt 8.4) an den rechten Rand des Fensters gesetzt und über die gesamte Fensterhöhe gespannt (Zeile 05). Die Auswahlliste erhält die Option yscrollcommand und stellt damit die Verbindung zum Rollbalken her (Zeile 08). Da die Auswahlliste nach dem Rollbalken erstellt wurde, muss die Verbindung von Rollbalken und Liste über die Methode config() hergestellt werden (Zeile 16). Das Ergebnis dieses Skripts zeigt die folgende Abbildung:
Abbildung 8.21: Auswahlliste mit Scrollbar Bei diesem Beispiel haftet der Rollbalken am rechten Rand des Fensters. In der Regel will man aber mehrere Widgets im Fenster platzieren, so dass ein Rollbalken am rechten Fensterrand, der eine Auswahlbox irgendwo anders im Fenster steuert, keinen Sinn macht. In diesem Fall gruppiert man Listbox und Scrollbar mit einem Frame-Widget (siehe Abschnitt 8.3.17) und definiert diesen Rahmen als Master für beide Widgets. Dadurch werden beide Elemente quasi zusammengebunden und sie können über den Frame positioniert werden: Listing 8.14: Scrollbar an Auswahlliste gebunden 01 02 03 04 05 06 07 08 09 10 11 12 13 14
from Tkinter import * f = Tk() f.geometry('200x240') # Frame erstellen fr= Frame(f) fr.pack()
# Scrollbar im Frame sb = Scrollbar(fr) sb.pack(side=RIGHT, fill=Y) # Listbox im Frame l = Listbox(fr,selectmode=BROWSE,
206
8.3 Die einzelnen Widget-Klassen
15 16 17 18 19 20 21 22 23 24 25
yscrollcommand=sb.set) sprachen = ['Assembler', 'awk', 'Basic', 'C', 'C++', 'C#', 'Delphi', 'Fortran', 'Java', 'Logo', 'Pascal', 'Perl', 'PHP', 'Prolog', 'Python','Ruby'] for sprache in sprachen: l.insert(END,sprache) l.pack() sb.config(command=l.yview) f.mainloop()
Das Ergebnis dieses Skripts sieht folgendermaßen aus:
Abbildung 8.22: Auswahlliste und Scrollbalken zusammen
8.3.15
Spinbox
Eine Spinbox ist ein Eingabefeld, dem schon eine Werteliste hinterlegt ist, die man über Buttons durchscrollen kann. Der Benutzer kann einen Wert aus der Liste auswählen oder alternativ einen eigenen Wert eintragen. Mit folgenden Optionen lässt sich das Widget konfigurieren: Option
Erläuterung
from_
Bestimmt den Anfangswert der Werteliste.
to
Bestimmt den Endwert der Werteliste.
increment
Bestimmt die Schrittweite in der Werteliste. Der Standardwert ist 1.0.
values
Ein Tupel, das als Werteliste verwendet wird. Überschreibt die Optionen from_, to und increment.
Eine oder mehrere Werte werden ab der Position index in die Werteliste eingefügt.
get(first,[last])
Gibt den Inhalt an der Position first in der Werteliste zurück. Wenn der zweite Parameter verwendet wird, werden die Positionen von first bis last zurückgegeben.
delete(first,[last])
Löscht die Position first in der Werteliste. Wenn der zweite Parameter verwendet wird, werden die Positionen von first bis last gelöscht.
Tabelle 8.18: Einige Methoden des Spinbox-Widget Das folgende Beispiel implementiert eine Spinbox, deren Werte einem Tupel entstammen: Listing 8.15: Erstellen einer Spinbox 01 02 03 04 05 06 07 08 09 10
Geht es um mehrzeiligen Text, den der Benutzer eingeben und bearbeiten soll, dann ist das Text-Widget gefragt. Es erzeugt ein Textfeld mit einer einstellbaren Anzahl von Zeilen. Dieses hat folgende spezielle Optionen:
208
8.3 Die einzelnen Widget-Klassen
Option
Erläuterung
width
Die Breite des Felds. Angegeben wird die Anzahl der Zeichen, die in eine Zeile passen soll.
height
Die Höhe des Felds. Angegeben wird die Anzahl der Zeilen.
wrap
Bestimmt, wie Zeilen umbrochen werden. Es sind drei Werte möglich: CHAR – Umbruch erfolgt nach dem letzten Buchstaben, der in die
Zeile passt (Standardeinstellung). WORD – Umbruch erfolgt nach dem letzten Wort, das in die Zeile
passt. NONE – es erfolgt kein Umbruch. tabs
Setzt die Tabulatorabstände. Übergeben wird ein Tupel mit den Abständen in cm, z. B. (1c,2c,3c). Mit dieser Angabe ist der erste Tabstopp bei 1 cm, der zweite bei 2 cm und der dritte bei 3 cm.
highlightcolor
Bestimmt die Farbe von ausgewähltem Text.
highlightbackground
Bestimmt die Hintergrundfarbe von ausgewähltem Text.
Tabelle 8.19: Optionen des Text-Widget Das Text-Widget hat einige Methoden für den Zugriff auf den Inhalt: Methode
Erläuterung
get(index1,[index2])
Gibt den Text von index1 bis index2 zurück. Wird der zweite Index nicht angegeben, wird nur das Zeichen an der Position index1 zurückgegeben.
insert(index, text)
Fügt an der Position index Text ein.
delete(index1,[index2])
Löscht den Text von index1 bis index2. Wird der zweite Index nicht angegeben, wird nur das Zeichen an der Position index1 gelöscht.
Tabelle 8.20: Methoden für den Textzugriff
Bestimmung von Indizes Besonders ist beim Text-Widget die Bestimmung des Index eines Zeichens. Dieser erfolgt nicht wie sonst üblich über seine Position im Gesamttext, sondern es wird immer die Position des Zeichens in der Zeile benannt. Der Index wird also über die Angabe der Zeilennummer und der Position des Zeichens in dieser Zeile bestimmt. Die Zählung der Zeilen beginnt mit 1, die der Zeichen mit 0. Der Index wird als Zeichenkette in der Form '3.7'
209
8 Grafische Benutzeroberflächen
angegeben. Diese Angabe bezeichnet das achte Zeichen in der dritten Zeile. Daneben gibt es die Form '3.end'
die den Index des letzen Zeichens einer Zeile, hier der dritten, wiedergibt. Des Weiteren existieren einige Konstanten, die Indizes wiedergeben: INSERT – bezeichnet die Indexposition der Einfügemarke. CURRENT – bezeichnet den Index des Zeichens, das sich am nächsten an der aktuellen
Mausposition befindet. END – bezeichnet den Index des letzten Zeichens im Gesamttext. SEL_FIRST – bezeichnet den Index am Anfang einer Auswahl. SEL_LAST – bezeichnet den Index am Ende einer Auswahl.
Das folgende Beispiel erzeugt ein Textfeld und fügt an verschiedenen Positionen Text ein. Listing 8.16: Erzeugen eines Textfelds und Einfügen von Text 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19
from Tkinter import * f = Tk() f.geometry('240x180') t = Text(f,width=120, height=16, tabs=('1c','2c','3c','4c'))
t.pack() # Text mit Tabs einfügen for l in range(1,5): tabs = '\t'*l text = tabs+'Tab'+str(l)+'\n' t.insert(END,text)
# Text in einigen Zeilen einfügen t.insert('1.end',' an das Zeilenende') t.insert('3.3',' mitten rein ')
f.mainloop()
Das Textfeld wird mit einer Angabe für Tabstopps erzeugt (Zeile 07). Mit einer Schleife wird in die ersten vier Zeilen Text eingefügt, der an den Tabstopp-Positionen beginnt (Zeile 11–14). Es wird immer am jeweiligen Textende eingefügt. Dann wird noch Text am Ende der ersten Zeile und mitten in die dritte Zeile eingefügt. Hierfür werden zwei weitere Formen der Indexbestimmung verwendet (Zeile 17 und 18).
210
8.3 Die einzelnen Widget-Klassen
Das erzeugte Textfeld sieht dann folgendermaßen aus:
Abbildung 8.24: Textfeld mit eingefügtem Text
Markierungen im Text setzen Man kann Markierungen in den Text setzen, die Positionen im Text beschreiben, die vom Index abgekoppelt sind. Damit lässt sich eine Hintergrundstruktur in den Text ziehen, die unabhängig von Einfügen und Löschen ist. Man setzt etwa eine Marke vor jede Überschrift oder jeden Kapitelanfang und schafft darüber eine Art Gliederung. Textmarken »wandern« mit. Wenn vor einer Marke Text eingefügt wird, bleibt er nicht an ihrer Indexposition, sondern sie »haftet« am Text. Ist zum Beispiel eine Überschrift mit einer Marke versehen, z. B. zu Beginn der Überschriftzeile, und wird dann vor der Überschrift ein Textblock eingefügt, so bleibt die Marke am Beginn der Überschriftzeile. Fragt man ihren Index ab, so hat sich dieser nach dem Einfügen verändert. Marken erhalten Namen und sind darüber referenzierbar. Es lassen sich also über Marken Textstellen jederzeit wieder auffinden. Für das Anlegen und Verwalten von Marken stehen einige Methoden zur Verfügung: Methode
Erläuterung
mark_set(name, index)
Fügt an der Position index eine Markierung mit dem angegebenen Namen ein.
mark_unset(name)
Löscht die Markierung mit dem angegebenen Namen.
mark_gravity(name,move)
Bestimmt, wie sich die Markierung beim Einfügen von Text an ihrer Position (Index) verhält. Gibt man für move den Wert LEFT ein, bleibt die Markierung an ihrer Position. Gibt man den Wert RIGHT ein, verschiebt sich die Markierung mit dem eingefügten Text.
mark_names()
Gibt eine Sequenz mit allen Marken im Text zurück.
index(name)
Gibt den Index der angegebenen Markierung zurück.
Tabelle 8.21: Methoden für die Arbeit mit Markierungen
211
8 Grafische Benutzeroberflächen
Mit Tags arbeiten Tags sind Markierungen mit einem Anfangs- und einem Endpunkt. Sie beschreiben einen Abschnitt im Text, der vom Index abgekoppelt ist. Fügt man vor dem durch das Tag beschriebenen Textabschnitt neuen Text ein oder löscht Text, verschiebt sich das Tag und umfasst weiterhin den ursprünglichen Textabschnitt. Fügt man innerhalb des Abschnitts, der durch das Tag definiert ist, Text ein, wird er dem Tag hinzugefügt, der Abschnitt erweitert sich damit. Tags haben wie Markierungen Namen, über die sie angesprochen werden. Sie werden verwendet, um den Textabschnitt, den sie beschreiben, zu formatieren Für die Verwaltung von Tags besitzt die Klasse Text eine Reihe von Methoden: Methode
Erläuterung
tag_add(name,index1,[index2]
Definiert einen Tag zwischen index1 und index2 und weist ihm den angegebenen Namen zu. Wird der zweite Parameter nicht verwendet, bezieht sich das Tag nur auf den Buchstaben an index1.
tag_config(name, option=wert, ...)
Formatiert das Tag über verschiedene Optionen (siehe Tabelle 8.25).
tag_cget(name,option)
Gibt den Wert der angegebenen Option des angegebenen Tags zurück.
tag_delete(name)
Löscht das Tag mit dem angegebenen Namen.
tag_bind(name, event, func)
Bindet einen Event an das angegebene Tag. Ausgeführt wird die mit func bezeichnete Funktion, wenn der Event eintritt.
tag_unbind(name, event)
Löst die Bindung zwischen angegebenem Tag und dem Event.
Tabelle 8.22: Methoden für die Arbeit mit Tags In der Regel wird man Tags setzen wollen, deren Abschnitt durch eine Textauswahl bestimmt ist. Hierfür können die Konstanten SEL_FIRST und SEL_LAST genutzt werden: tag_add('eine_auswahl',SEL_FIRST, SEL_LAST)
Die Formatierung eines Textabschnitts, der durch ein Tag definiert ist, erfolgt über Optionen, die die folgende Tabelle auflistet: Option
Erläuterung
background
Hintergrundfarbe
borderwidth
Breite des Rahmens, der um den Abschnitt gezogen wird
Tabelle 8.23: Optionen für Tags
212
8.3 Die einzelnen Widget-Klassen
Option
Erläuterung
font
Schrifteinstellung (siehe Abschnitt 8.2.1)
foreground
Schriftfarbe
justify
Ausrichtung, mögliche Werte sind LEFT, RIGHT, CENTER.
lmargin1
Linker Abstand der ersten Zeile eines Abschnitts
lmargin2
Linker Abstand aller Zeilen außer der ersten eines Abschnitts
offset
Vertikaler Abstand eines Abschnitts. Positive Werte bestimmen den Abstand oberhalb des Abschnitts, negative Werte unterhalb des Abschnitts.
overstrike
Wird der Wert auf 1 gesetzt, wird der Text im Abschnitt durchgestrichen.
relief
Darstellung eines Rahmens um den Abschnitt. Standardwert ist FLAT.
rmargin
Rechter Abstand des Abschnitts
spacing1
Abstand oberhalb des Abschnitts
spacing2
Zeilenabstand innerhalb des Abschnitts
spacing3
Abstand unterhalb des Abschnitts
tabs
Tabstopp-Einstellung für den Abschnitt
underline
Wird der Wert auf 1 gesetzt, wird der Text im Abschnitt unterstrichen.
wrap
Bestimmt die Art des Zeilenumbruchs im Abschnitt.
Tabelle 8.23: Optionen für Tags (Forts.) Das folgende Beispiel zeigt einige Textformatierungen mit Tags: Listing 8.17: Erzeugung von Textformatierungen 01 02 03 04 05 06 07 08
from Tkinter import * f = Tk() f.geometry('240x180') t = Text(f,width=120, height=16, wrap=WORD) t.pack()
text = """Lorem ipsum dolor sit amet, consetetur sadipscing elitr. 09 Sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. 10 Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.""" 11 12 t.insert(END,text) 13 14 t.tag_add('absatz','2.0','2.end')
Es werden drei Tags gesetzt. Der erste umfasst einen kompletten Absatz (Zeile 14). Der zweite umfasst den Anfangsbuchstaben des Absatzes (Zeile 15) und der dritte ein Wort innerhalb des Absatzes (Zeile 16). An diesem Beispiel ist zu sehen, dass innerhalb durch Tags definierten Abschnitten weitere Tags enthalten sein können. Die definierten Abschnitte werden dann formatiert. Für den Absatz werden Abstände definiert (Zeile 18), der Anfangsbuchstabe wird größer und in einer anderen Schrift dargestellt (Zeile 19). Außerdem wird das einzelne Wort unterstrichen (Zeile 20). Die folgende Abbildung zeigt das Ergebnis:
Abbildung 8.25: Formatierter Text
8.3.17
Frame
Wie bereits an anderer Stelle erwähnt, dient das Frame-Widget der Organisation der Oberfläche. Es kann für andere Widgets als Master fungieren, ist also ein Container, der andere Widgets aufnehmen kann. In der Grundeinstellung ist ein Frame nicht sichtbar. Ein Rahmen kann gesetzt werden, indem man der Option einen anderen als den Standardwert FLAT zuweist (siehe Abschnitt 8.2.1). Das folgende Beispiel fasst zwei von insgesamt drei Buttons zu einer Layoutgruppe zusammen:
214
8.3 Die einzelnen Widget-Klassen
Listing 8.18: Gruppieren mit dem Frame-Widget 01 02 03 04 05 06 07 08 09 10 11 12 13 14
from Tkinter import * f = Tk() fr = Frame(f,relief=GROOVE,bd=2,padx=10,pady=10)
Entscheidend für Zusammenfassung von Widgets zu einer Gruppe ist die Angabe des Masters bei den Widgets (Zeile 07 und 09). Der dritte Button gehört nicht zum Frame-Widget, weil er dem Hauptfenster zugeordnet ist. Führt man die Methode pack() für das Frame nicht aus, werden die Buttons zwar gebildet, aber nicht dargestellt, obwohl pack() für sie ausgeführt wurde. Man kann dadurch ganze Layoutgruppen von der Oberfläche entfernen und wieder hinzufügen (siehe Abschnitt 8.4). Das obige Beispiel sieht im Ergebnis folgendermaßen aus:
Abbildung 8.26: Gruppierte Elemente mit dem Frame-Widget
8.3.18
LabelFrame
Ein LabelFrame übt die gleiche Funktion aus und wird genauso angewendet wie ein einfacher Frame. Der Unterschied besteht darin, dass ein LabelFrame eine Beschriftung haben kann und immer einen Rahmen hat. Die Beschriftung wird mit der Option text bestimmt. Das Beispiel aus dem vorigen Abschnitt muss nur in der Definition des Frame-Widget geändert werden: 04 fr = LabelFrame(f,text='Kontrolle',bd=2,padx=10,pady=10)
215
8 Grafische Benutzeroberflächen
Damit sieht der Rahmen nun so aus:
Abbildung 8.27: Gruppierung mit dem LabelFrame
8.3.19
PanedWindow
Mit dem PanedWindow-Widget lässt sich eine flexible Fensterteilung realisieren. Es wird eine Trennlinie angezeigt, die sich verschieben lässt. In beide Teilbereiche des Widget können andere Widgets eingefügt werden. Mit folgenden Optionen lässt sich das Widget konfigurieren. Option
Erläuterung
orient
Bestimmt die Ausrichtung der Teilung. Standardwert ist HORIZONTAL. Der andere mögliche Wert ist VERTICAL.
sashwidth
Bestimmt die Breite der Trennlinie in Pixel. Standardwert ist 2.
sashrelief
Bestimmt die Form der Trennlinie. Standardwert ist RELIEF.
showhandle
Bestimmt, ob ein Anfasser zum Verschieben an der Trennlinie angebracht wird. Standardwert ist 0.
handlesize
Bestimmt die Größe des Anfassers. Standardwert ist 8.
handlepad=10,
Bestimmt den Abstand des Anfassers von oben bzw. von links. Standardwert ist 8.
Tabelle 8.24: Option des PanedWindow-Widget Um ein Widget in einem der beiden Teilbereiche des Fensters zu platzieren, reicht es nicht aus, die Methode pack() zu verwenden. Zwar dient das PanedWindow auch als Master für Widgets, aber es muss zusätzlich die Methode add() aufgerufen werden, um Widgets zu platzieren. Der erste Aufruf von add() platziert ein Widget im ersten Teilfenster, der zweite Aufruf platziert ein Widget im zweiten Teilfenster. Das folgende Beispiel zeigt, wie es geht: Listing 8.19: Erzeugen einer Fensterteilung 01 from Tkinter import * 02 f = Tk() 03
Das Beispiel erzeugt eine Fensterteilung mit seiner Standardeinstellung (Zeile 04). Die beiden Label werden mit der Methode add() auf die Teilbereiche verteilt (Zeile 13 und 14). Es kann also nur jeweils ein Widget in einen Teilbereich eingefügt werden. Will man mehrere Widgets einfügen, muss man diese zunächst mit einem Frame gruppieren und diesen Frame dann einfügen. Es ist auch möglich, ein weiteres PanedWindow in einen Teilbereich einzufügen und damit Mehrfachunterteilungen zu realisieren. Die folgende Abbildung zeigt das Ergebnis des obigen Beispiels:
Abbildung 8.28: Fenster mit flexibler Teilung
8.3.20 Toplevel Das Toplevel-Widget erzeugt ein weiteres Fenster, das abhängig vom Hauptfenster ist. Schließt man das Hauptfenster, wird auch das Fenster, das mit dem Toplevel-Widget erzeugt wurde, geschlossen. Es braucht keinen Layoutmanager, um das Fenster darzustellen. In das Fenster können alle Widgets in gewohnter Weise platziert werden. Diese müssen das neue Fenster als Master verwenden. Alle Methoden, die auf das Hauptfenster anwendbar sind, können auch für das Toplevel-Widget verwendet werden (siehe Abschnitt 8.3.1). Das folgende Beispiel erzeugt ein Werkzeugfenster: Listing 8.20: Erzeugung eines weiteren Fensters 01 from Tkinter import * 02 f = Tk() 03 f.geometry('240x180')
Für eine gute Benutzeroberfläche bedarf es eines übersichtlichen Layouts, das die Funktionen und Informationen gut strukturiert. Mit Frames ist es möglich, Widgets zu gruppieren. Ihre exakte Anordnung innerhalb ihres Master ist dadurch aber nicht zu erreichen. Dafür werden sogenannte Layoutmanager verwendet. Sie sind zum einen für die Darstellung an sich zuständig. Kein Widget wird auf der Oberfläche dargestellt, wenn nicht ein Layoutmanager angewandt wurde. Zum anderen kann man sie mit entsprechenden Optionen dazu bewegen, ein Widget an einer bestimmten Position im Fenster zu platzieren. Technisch gesehen gehören die Layoutmanager zu den Widget-Methoden. In fast allen bisherigen Beispielen wurde die Methode pack() auf die Widgets angewendet, um sie darzustellen. Tkinter besitzt insgesamt drei Methoden für das Layoutmanagement: pack() – wird verwendet, um Widgets in Bezug zu anderen Widgets zu platzieren,
z. B. links oder über andere Widgets. grid() – erstellt ein unsichtbares Layoutraster, in dessen Zellen Widgets eingefügt
werden. place() – ermöglicht die exakte Positionierung von Widgets in ihrem Master.
218
8.4 Layoutmanager
Alle Widgets haben spezifische Optionen, mit den die Positionierung vorgenommen wird. Werden keine Optionen angegeben, agiert die Methode mit ihren Voreinstellungen. Tipp Innerhalb eines Masters sollte man nur eine Form des Layoutmanagements wählen. Andernfalls kann es zu Darstellungsproblemen kommen. Das heißt, wenn man z. B. in einem Frame zwei Buttons platzieren will, sollte man für beide den gleichen Layoutmanager verwenden. In einem zweiten Frame kann dann durchaus ein anderer Manager verwendet werden.
8.4.1
Die Methode pack()
Die Methode pack() platziert ein Widget zu bereits existierenden Widgets im Master. Dieser wird in seiner Größe so verändert, dass das neue Element hineinpasst. Wurde der Master mit bestimmten Abmessungen definiert, wird er durch pack() so angepasst, dass die Elemente genau hineinpassen. Mit anderen Worten: Größeneinstellungen für den Master werden ignoriert. Eine Ausnahme bildet hier der Master auf der obersten Ebene (Hauptfenster oder Toplevel). Ohne Angabe von Optionen positioniert pack() Widgets untereinander in der Mitte von unsichtbaren Zeilen. Mit folgenden Optionen lässt sich die Positionierung beeinflussen: Option
Erläuterung
anchor
Das Widget wird entsprechend der Himmelsrichtungen im Master platziert. Die Zeilengrenze wird nicht aufgehoben. Mögliche Werte sind: N – oben (Norden) NE – oben rechts (Nordosten) E – rechts (Osten) SE – unten rechts (Südosten) S – unten (Süden) SW – unten links (Südwesten) E – links (Osten) NW – oben links (Nordosten) CENTER – zentriert
Tabelle 8.25: Optionen der Methode pack( )
219
8 Grafische Benutzeroberflächen
Option
Erläuterung
side
Das Widget wird an eine der vier Seiten des Masters gesetzt. Das Widget wird aus seiner Zeile herausgenommen. Mögliche Werte sind: TOP – oberer Rand LEFT – linker Rand BOTTOM – unterer Rand RIGHT – rechter Rand
Bestimmt, ob und wie sich das Widget der Größe seines Masters anpasst.
fill
Mögliche Werte: X – horizontale Anpassung Y – vertikale Anpassung. Dies funktioniert aber nur, wenn das Widget aus seiner Zeile herausgenommen ist oder expand =1 gesetzt ist. BOTH – Anpassung in beide Richtungen
Bestimmt, ob eine Größenänderung des Fensters eine Größenänderung des Widget nach sich zieht. Der Standardwert ist 0.
expand
Tabelle 8.25: Optionen der Methode pack( ) (Forts.) Für das nächste Beispiel gibt es folgende Grundanordnung: Listing 8.21: Die Methode pack( ) ohne Optionen 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
from Tkinter import * f = Tk() f.geometry('240x180') l1 l2 l3 l4
Abbildung 8.30: Auswirkung von fill und expand Label 1 und Label 4 nehmen vertikal den Raum ein, der möglich ist. Dies gelingt aber nur, weil bei beiden die Option expand gesetzt ist. Label 3 und Label 4 nehmen horizontal die gesamte Fensterbreite ein. Die nächste Konfiguration zeigt die Verwendung der side-Option: Listing 8.23: Verwendung von side 10 11 12 13
Und so sieht dies aus: Label 1 wird an den linken, Label 4 an den unteren Rand »geklebt«. Label 2 und 3 rutschen eine Zeile nach oben, da Label 1 aus der Zeilenreihenfolge herausgenommen ist.
221
8 Grafische Benutzeroberflächen
Abbildung 8.31: Auswirkung der side-Option Schließlich ein Beispiel für die anchor-Option: Listing 8.24: Verwendung von anchor 10 11 12 13
Abbildung 8.32: Auswirkung der anchor-Option Weil die anchor-Option die Zeilenreihenfolge nicht durchbricht, hat für Label 2 die Einstellung SE nur den Effekt, dass es nach rechts rutscht. Nach unten wird es von Label 3 begrenzt.
8.4.2
Die Methode grid()
Mit der Methode grid() wird ein unsichtbares Layoutraster aus Zeilen und Spalten aufgezogen. In die dabei entstehenden Zellen werden die Widgets positioniert. Dabei ist es wie bei HTML-Tabellen möglich, Zellen vertikal oder horizontal miteinander zu verbinden. Die Reihen sind von oben nach unten beginnend mit 0 durchnummeriert, die Spalten von links nach rechts, ebenfalls beginnend mit 0.
222
8.4 Layoutmanager
Die Größe der Zelle richtet sich nach dem Widget, das in sie positioniert wird. Das hat auch Auswirkungen auf die Zellen in gleicher Reihe und gleicher Spalte. Innerhalb der Zelle wird das Widget zentriert positioniert. Das kann mit der entsprechenden Option (sticky) modifiziert werden. Die Optionen der Methode grid() im Einzelnen: Option
Erläuterung
column
Spaltennummer
row
Reihennummer
columnspan
Anzahl der Spalten, über die sich das Widget spannen soll
rowspan
Anzahl der Reihen, über die sich das Widget spannen soll
sticky
Positioniert das Widget in der Zelle. Positionierung in einer Ecke mit NW, NE, SE oder SW Positionierung an einem Rand mit N, E, S oder W Streckung des Widget horizontal mit E+W Streckung des Widget vertikal mit N+S Streckung des Widget in beide Richtungen mit N+E+S+W
padx
Innenabstand der Zelle in horizontaler Ausrichtung
pady
Innenabstand der Zelle in vertikaler Ausrichtung
ipadx
Innenabstand des Widget in horizontaler Ausrichtung
ipady
Innenabstand des Widget in vertikaler Ausrichtung
Tabelle 8.26: Optionen der Methode grid( ) Das folgende Beispiel zeigt den Aufbau einer Zifferntastatur und verwendet dabei ein Layoutraster: Listing 8.25: Verwendung eines Layoutrasters 01 02 03 04 05 06 07 08 09 10 11 12 13
from Tkinter import * f = Tk() # Raster als Liste (Spalte,Reihe,Aufschrift) raster=[(0,0,'7'),(1,0,'8'),(2,0,'9'),(3,0,'/'), (0,1,'4'),(1,1,'5'),(2,1,'6'),(3,1,'*'), (0,2,'1'),(1,2,'2'),(2,2,'3'),(3,2,'-'), (0,3,'0'),(2,3,'%'),(3,3,'+')] # Buttons erzeugen for (c,r,t) in raster: b = Button(f,text=t, width=4, height=2,
223
8 Grafische Benutzeroberflächen
14 font=('Arial',14,'bold')) 15 if t != '0': 16 b.grid(column=c,row=r) 17 else: 18 # 0-Button über zwei Reihen spannen 19 b.grid(column=c,row=r,columnspan=2,sticky=E+W) 20 f.mainloop()
Das Raster wird zunächst in einer Liste aufgebaut (Zeile 04–07). Jedes Listenelement ist ein Tupel mit drei Werten: Spaltennummer, Reihennummer und Beschriftung des Buttons. Über diese Liste wird iteriert und für jeden Eintrag ein Button-Objekt erzeugt (Zeile 10–14). Für alle Buttons wird die Methode grid() aufgerufen und die Spalten- und Reihennummer übergeben (Zeile 16). Wenn es zur Nulltaste kommt, werden zwei Spalten zusammengefügt und es wird dafür gesorgt, dass der Button die gesamte Breite ausfüllt (Zeile 19). Das Ergebnis sieht folgendermaßen aus:
Abbildung 8.33: Aufbau einer Rechnertastatur mit einem Layoutraster
8.4.3
Die Methode place()
Die Methode place() kann Widgets exakt über Pixel- oder Zentimeterangaben im Master positionieren. Dessen Koordinatensystem hat seinen Ursprung (0,0) in der linken oberen Ecke. Positioniert wird durch die Angabe von Werten für die x- und die y-Koordinate eines Ankerpunkts des Widget. Dieser ist voreingestellt auf die linke obere Ecke des Widget.
224
8.4 Layoutmanager
Folgende Optionen kann die Methode place() haben: Option
Erläuterung
x
Die x-Position des Widget in Pixel oder cm
y
Die y-Position des Widget in Pixel oder cm
relx
Die relative x-Position des Widget. Die x-Position ergibt sich aus der Breite des Master, multipliziert mit dem hier angegebenen Wert. Werte größer als 1.0 positionieren das Widget außerhalb des Fensters
rely
Die relative y-Position. Wie relx, aber mit der Bezugsgröße Höhe des Masters.
anchor
Bestimmt den Ankerpunkt des Widget. Dieser Punkt wird auf die angegebene Koordinate platziert. Mögliche Werte sind: NE – links oben NW – rechts oben SW – rechts unten SE – links unten
width
Breite des Widget
height
Höhe des Widget
relwidth
Die relative Breite des Widget Berechnet sich aus der Breite des Master, multipliziert mit dem hier angegebenen Wert. Werte größer als 1.0 ergeben Widgets, die breiter als das Master sind.
relheight
Die relative Höhe des Widget. Wie relwidth, aber mit der Bezugsgröße Höhe des Master.
Tabelle 8.27: Optionen der Methode pack( ) Das folgende Beispiel positioniert ein Widget absolut und ein zweites relativ. Gleichzeitig wird auch die Breite des zweiten Widget relativ angegeben: Listing 8.26: Absolute und relative Positionierung mit place() 01 02 03 04 05 06 07 08 09 10
from Tkinter import * f = Tk() l1 = Label(f,text='Label 1', relief=GROOVE) l1.place(x=10, y=60)
Ankerpunkt bei beiden Labels ist die linke obere Ecke. Im Vergleich zweier Fenstergrößen sieht man den Unterschied:
225
8 Grafische Benutzeroberflächen
Abbildung 8.34: Absolute und relative Positionierung im Vergleich Während Label 1 fest an seinem Platz bleibt und seine Größe nicht ändert, verschiebt sich Label 2 nach rechts und wird breiter. Der Abstand zum linken Rand und die Breite machen weiterhin 30% der Fensterbreite aus.
8.4.4
Weitere Layoutmethoden
Während des Programmlaufs ist es häufig notwendig, Fensteransichten auszutauschen oder auch innerhalb einer Ansicht dynamische Modifikationen vorzunehmen. Dazu stehen neben den drei in den vorigen Abschnitten besprochenen Layoutmanagern weitere Layoutmethoden zur Verfügung. Die Zuordnung ergibt sich aus dem Präfix des jeweiligen Methodennamens. So wird z. B. pack_configure() zum Setzen von neuen Optionen verwendet, wenn ein Widget zuvor mit pack() positioniert wurde. Wurde place() verwendet, bedient man sich der Methode place_configure(). Die folgende Tabelle erläutert die Methoden: Methode
Erläuterung
pack_configure()
Die Optionen des Widget werden neu gesetzt. Die Übergabe der Werte erfolgt in der Form option=wert. Es muss die zum jeweiligen Layoutmanager passende Methode verwendet werden.
Liefert entsprechend des jeweiligen Layoutmanagers ein Dictionary mit den Layoutoptionen und ihren Werten. Gibt eine Liste mit den Instanzen aller Widgets im Layout zurück. Wird als Methode des Masters aufgerufen. Das Widget wird aus dem Master entfernt. Es wird dabei nicht gelöscht, sondern nur aus der Darstellung entfernt.
Tabelle 8.28: Methoden für das Layoutmanagement
226
8.5 Praxis: Oberfläche für Serienbriefprogramm
Methode
Erläuterung
pack_propagate()
Wird die Methode mit dem Wert 0 als Parameter für ein Widget aufgerufen, verändert es nicht die Größe seines Master.
grid_propagate() grid_remove()
Hat die gleiche Auswirkung wie grid_forget(). Es bleiben aber die Layouteinstellungen des Widget erhalten, so dass es mit grid() ohne Angabe von Optionen wieder so platziert wird wie ursprünglich.
grid_columnconfigure(index, **options)
Konfiguration für eine einzelne Spalte eines Layoutrasters, die durch index definiert ist.
grid_rowconfigure(index, **options)
Konfiguration für eine einzelne Reihe eines Layoutrasters, die durch index definiert ist.
Tabelle 8.28: Methoden für das Layoutmanagement (Forts.)
8.5
Praxis: Oberfläche für Serienbriefprogramm
In Abschnitt 6.4 wurde ein kleines Programm entwickelt, das einen Serienbrief aus einer Adresssammlung generiert, und in Abschnitt 6.6 wurde das Programm um die Funktion erweitert, Adressdaten hinzuzufügen. Es soll nun eine grafische Oberfläche entwickelt werden, in der man zum einen den Musterbrief editieren und zum anderen neue Adressdaten eingeben kann. Und natürlich soll das Programm auch wieder die Serienbriefe generieren.
8.5.1
Der Grundaufbau
Es wird eine Klasse entwickelt mit dem Namen SerialLetter. Deren Konstruktormethode ist dafür zuständig, ein Programmfenster zu erzeugen und die Methoden aufzurufen, die für den Aufbau der Oberfläche zuständig sind. Eine dieser Methoden kümmert sich um den Aufbau des Menüs, eine um die Oberfläche, die den Musterbrief bearbeitet und die dritte baut die Oberfläche für die Adresseingabe ein. Daneben gibt es acht weitere Methoden für die Programmfunktionalität. Man muss bedenken, dass es sich hier um eine Programmskizze handelt. Ein »ausgewachsenes« Programm, das den Rahmen des Buchs mehr als sprengen würde, hätte weitaus mehr Methoden.
227
8 Grafische Benutzeroberflächen
Und so sieht der Grundaufbau der Klasse aus: SerialLetter master# das Programmfenster ab# Adressbestand als Liste
# viele Widgets werden ebenfalls als Attribute definiert. __init__()
# Bildung der Oberfläche createMenu() musterbriefGUI() adressenGUI()
Tabelle 8.29: Die Klasse SerialLetter Im Quelltext beginnt es wie immer mit den Importen und der Definition der Konstruktormethode: Listing 8.27: Importe, Klassendefinition, Konstruktor 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
# -*- coding: latin-1 -*# die notwendigen Importe import Tkinter as tk from Tkconstants import * import tkFileDialog as fd import tkMessageBox as mb from pickle import dump, load class SerialLetter():
# Liste für Adressen self.ab = [] # Aufruf der Methoden, die das GUI erstellen self.createMenu() self.musterbriefGUI() self.adressenGUI() self.master.mainloop()
Es beginnt mit einer Zeile, die die Zeichenkodierung bestimmt. Dies erlaubt es, Umlaute in Zeichenketten zu verwenden (Zeile 01). Der Import des Tkinter-Namensraums erfolgt hier nicht mit *, um Namenskonflikte (siehe Abschnitt 4.9) zu vermeiden (Zeile 04). In den kleinen Beispielen der vorigen Abschnitte war es kein Problem, alle Namen von Tkinter in den aktuellen Namensraum zu importieren. Bei größeren Projekten sollte man dies aber vermeiden. Man muss zwar jetzt mehr Schreibaufwand tätigen, da sich Widgets nicht mehr direkt aufrufen lassen, ab durch den Zusatz as tk beim Import kann der in Grenzen gehalten werden. Anders verhält es sich mit den Konstanten von Tkinter. Um diese jetzt nicht auch in der tk.LEFT oder tk.GROOVE notieren zu müssen, wird das Zusatzmodul Tkconstants importiert (Zeile3), das alle Tkinter-Konstanten noch mal separat enthält. Es werden dann noch die Module tkFileDialog und tkMessageBox benötigt, die Dialogfenster bzw. Hinweisfenster zur Verfügung stellen (Zeile 06 und 07), sowie die Methoden load() und dump() aus dem Modul pickle. Die Konstruktormethode erzeugt das Fenster, weist ihm einen Titel zu und bestimmt seine Größe (Zeilen 14–16). Das Attribut ab für die interne Verwaltung der Adressdaten wird initialisiert (Zeile 18) und die drei Methoden zur Bildung der Oberfläche werden aufgerufen (Zeilen 21–23). Schließlich wird das Fenster geöffnet und in »Arbeitsbereitschaft« versetzt (Zeile 25). Ruft man nun die Klasse auf, wird direkt ein Fenster geöffnet. Es bietet sich an, am Ende des Skripts den Klassenaufruf zu notieren, dann kann der Test direkt durch Aufruf des Skripts erfolgen: SerialLetter()
Im Moment aber würde es noch zu Fehlermeldungen kommen, weil die drei Methoden, die durch den Konstruktor aufgerufen werden, noch nicht definiert sind. Für die Tests sollten deshalb alle Methoden wie im Klassendiagramm gelistet in der Form def methodenName(self): pass
in das Skript geschrieben werden. In den folgenden Abschnitten werden diese Methoden nach und nach mit Leben gefüllt.
229
8 Grafische Benutzeroberflächen
8.5.2
Das Menü
Das Menü hat nur einen Punkt mit drei Unterpunkten. Es hat die Funktion, zwischen den Ansichten zu wechseln und die Serienbriefe zu generieren. Die Methode dazu sieht folgendermaßen aus: Listing 8.28: Die Methode zum Erstellen des Menüs 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
Es wird eine Menüleiste erstellt und dem Hauptfenster zugewiesen (Zeile 29 und 30). Diese Leiste erhält ein Menü mit dem Namen Bearbeiten (Zeile 33 und 34) und in dieses Menü werden zwei Radiobutton-Elemente sowie ein einfaches Command-Element eingefügt (Zeilen 38–42). Die Radiobuttons dienen der Auswahl der Oberflächenansichten. Hier kann immer nur eine angezeigt werden. Alle Menüpunkte erhalten für die command-Option den Namen einer Methode, die die spezifische Funktion implementiert. Das Programm kann nun gestartet werden und erzeugt folgendes Fenster:
230
8.5 Praxis: Oberfläche für Serienbriefprogramm
Abbildung 8.35: Das Fenster mit Menü (verkleinert)
8.5.3
Die Musterbriefoberfläche
Die Musterbriefoberfläche soll ein Textfeld erhalten, in das eine Vorlage geladen und bearbeitet werden kann, sowie Buttons zum Öffnen und Speichern eines Briefs. Sie sieht folgendermaßen aus:
Es beginnt mit dem Rahmen, der als Master für die weiteren Elemente fungiert (Zeile 48). Es folgt dann ein unsichtbarer Rahmen, der dazu dient, das Textfeld und den Rollbalken zusammenzubinden (Zeile 54). Dann werden Rollbalken und Textfeld erzeugt (Zeile 58–65) und so konfiguriert, dass das Scrolling funktionieren kann. Ein weiterer Rahmen (Zeile 70) umgibt die beiden Buttons (Zeile 73–79), denen Methoden für ihre jeweiligen Funktionen zugeordnet sind.
232
8.5 Praxis: Oberfläche für Serienbriefprogramm
Der äußere Rahmen und das Textfeld sind als Attribute der Klasse definiert. Der Grund dafür ist, dass auf sie aus anderen Methoden heraus zugegriffen werden muss. Dort, wo das nicht der Fall ist, reichen einfache Variablen für die Widget-Instanzierung aus.
8.5.4
Die Adresseingabe
Die Oberfläche für die Adresseingabe benötigt die entsprechenden Eingabefelder und bekommt ein Feld, in dem alle Adressen angezeigt werden. Zwei Buttons regeln auch hier das Öffnen und Speichern von Adressdaten. Die folgende Abbildung zeigt die Adresseingabe:
Abbildung 8.37: Adressenansicht (mit geladenen Adressen) Die Methode, die diese Ansicht erzeugt, sieht so aus: Listing 8.30: Die Methode zur Erzeugung der Adressansicht 81 82 83 84 85
Auch hier wird zunächst ein Rahmen erzeugt, der als Master für die folgenden Elemente dient (Zeile 84). Ein weiterer Rahmen umschließt die Elemente zur Eingabe der Adressen (Zeile 90). Diese Elemente werden in den Rahmen mit der Methode place() absolut positioniert (Zeile 94–128). Das erste Element ist ein Auswahlmenü für die Anrede. Es benötigt eine Kontrollvariable, die den aktuellen Wert enthält (Zeile 94). Alle übrigen Felder sind Eingabefelder, deren Wert direkt ausgelesen werden kann. Ein Button (Zeile 131) dient dem Hinzufügen einer neuen Adresse. Unterhalb der Eingabe wird ein Listenfeld positioniert, das die geladenen und hinzugefügten Adressen anzeigt (Zeile 136). In einem dritten Rahmen werden die beiden Buttons platziert (Zeile 140–150). Auch in dieser Methode sind die Elemente, auf die von anderen Methoden zugegriffen werden muss, als Attribute definiert. Und natürlich sind alle Buttons mit Methoden verbunden, die die jeweilige Funktionalität bereitstellen.
8.5.5
Wechsel zwischen den Ansichten
Schaut man noch mal genau in Listing 8.30 in die Zeile 87, dann stellt man fest, dass diese auskommentiert ist. Entfernt man das Kommentarzeichen, würden beim Aufruf der Klasse sowohl die Musterbrief- als auch die Adressenansicht angezeigt. Diese sollen aber im Wechsel angezeigt werden. Aus diesem Grund wird für den äußeren Rahmen der Adressansicht der Layoutmanager zunächst nicht aufgerufen. Der Rahmen einschließlich des gesamten Inhalts erscheint dann nicht auf der Oberfläche. Gleichwohl sind alle Elemente instanziert und können deshalb schnell zur Ansicht gebracht werden. Damit das im Wechsel mit der Musterbriefansicht geschehen kann, muss diese verschwinden. Die beiden folgenden Methoden, die über das Menü aufgerufen werden, sorgen für den Wechsel:
235
8 Grafische Benutzeroberflächen
Listing 8.31: Die Methoden für den Ansichtswechsel 152 153 154 155 156 157 158 159 160
def showA(self): """ zeigt Adressbuch-Oberfläche """ self.mb_rahmen.pack_forget() self.ad_rahmen.pack(expand=1,fill=BOTH) def showM(self): """ zeigt Musterbriefoberfläche """ self.ad_rahmen.pack_forget() self.mb_rahmen.pack(expand=1,fill=BOTH)
Es ist die Methode pack_forget(), die die Ansichten zum Verschwinden bringt (Zeile 154 und 159). Beide Methoden funktionieren gleich: Die eine Ansicht wird von der Oberfläche entfernt, die andere durch Aufruf des Layoutmanagers angezeigt (Zeile 155 und 160). Es ist dabei notwendig, die entsprechenden Optionen an die Methode zu übergeben, damit es eine korrekte Darstellung gibt.
8.5.6
Funktionen der Musterbriefansicht
In der Musterbriefansicht kann man Briefe öffnen und speichern. Natürlich kann man auch im Textfeld schreiben, kopieren und einfügen, aber diese Funktionalität bringt das Feld schon mit. Weitere Editiermöglichkeiten sind nicht implementiert. Die Methoden zum Öffnen und zum Speichern bedienen sich zweier Dialogfenster, die sehr einfach über Methoden des Moduls tkFileDialog zu erhalten sind. Diese Methoden übernehmen die Kommunikation mit dem Betriebssystem und liefern die Datentypen zurück, die man im Programm benötigt. Zunächst die Methode zum Öffnen eines Musterbriefs: Listing 8.32: Methode zum Öffnen eines Musterbriefs 162 def openMB(self): 163 """ öffnet Musterbrief """ 164 path = fd.askopenfilename() 165 if path: 166 dat_obj = file(path,'r+') 167 text = dat_obj.read() 168 self.text.delete('0.1',END) 169 self.text.insert(END,text) 170 dat_obj.close() 171 del dat_obj
Der Aufruf der Methode askopenfilename() (Zeile 164) erzeugt ein Standard-Dialogfenster zum Öffnen von Dateien. Nach der Auswahl einer Datei liefert sie den absoluten Pfad zur Datei. Dieser kann nun verwendet werden, um die Datei zu öffnen (Zeile 166). Jetzt muss sie nur noch ausgelesen (Zeile 167) und der Inhalt in das Textfeld
236
8.5 Praxis: Oberfläche für Serienbriefprogramm
übertragen werden (Zeile 169). Das vorige Löschen des gesamten Inhalts des Textfelds (Zeile 168) ist notwendig, damit die neue Datei nicht an das Ende des bestehenden Inhalts angefügt wird. Es muss dann nur noch die Datei geschlossen und das Dateiobjekt gelöscht werden. Normalerweise müssten hier auch Abfragen eingebaut werden, die prüfen, ob bestehender Inhalt zunächst gespeichert werden soll. Sie werden in dieser Programmskizze aus Gründen der Übersichtlichkeit weggelassen. Das Modul tkFileDialog stellt noch die Methode askopenfile() für den Öffnen-Dialog zur Verfügung. Diese liefert gleich ein Dateiobjekt zurück. Nun die Methode zum Speichern des Musterbriefs: Listing 8.33: Methode zum Speichern des Textfeldinhalts 173 def saveMB(self): 174 """ sichert Musterbrief """ 175 path = fd.asksaveasfilename() 176 if path: 177 dat_obj = file(path,'w') 178 text = self.text.get('0.1',END) 179 dat_obj.write(text) 180 dat_obj.close() 181 del dat_obj
Hier ist es die Methode asksaveasfilename()()() (Zeile 175), die das Dialogfenster erzeugt. Nach der Wahl des Verzeichnisses und der Eingabe eines Namens für die Datei liefert auch sie einen absoluten Pfad zurück. Mit diesem kann eine Datei erzeugt werden (Zeile 177) und der Text aus dem Textfeld hineingeschrieben werden (Zeile 178 und 179). Auch hier bietet tkFileDialog eine Alternative mit asksaveasfile()(), die ein Dateiobjekt zurückliefert.
8.5.7
Funktionen der Adressenansicht
In der Adressenansicht geht es beim Öffnen und Speichern darum, die Adressdaten, die als Liste aus Dictionarys verwaltet werden, umzuwandeln in ein speicherbares bzw. wieder auslesbares Format (siehe Abschnitt 6.5). Hier die beiden zuständigen Methoden: Listing 8.34: Die Methoden zum Öffnen und Speichern von Adressdaten 183 184 185 186 187
dat_obj = file(path,'r') self.ab = load(dat_obj) dat_obj.close() del dat_obj # Anzeigeliste leeren self.anzeige.delete(0,END) # Daten in Anzeigeliste eintragen for item in self.ab: adr = '%(anrede)s %(vorname)s %(name)s, %(strasse)s, %(plz)s %(ort)s' %item self.anzeige.insert(END,adr) def saveAD(self): """ Sichert Adressbuchdatei """ # Dialogfenster anzeigen path = fd.asksaveasfilename() if path: dat_obj = file(path,'w') dump(self.ab,dat_obj) dat_obj.close() del dat_obj
Das Öffnen einer Adressdatei erfolgt wie im Abschnitt zuvor beschrieben. Mit der Methode load() des Moduls pickle wird der Dateiinhalt direkt in das notwendige Datenformat umgewandelt (Zeile 189). Das Ergebnis ist eine Liste mit Dictionarys, die dem Attribut ab zugewiesen wird. Für die Anzeige der Adressen wird eine Zeichenkette erzeugt und zeilenweise in die Liste eingefügt (Zeile 195–197). Beim Speichern wird die Liste in ein schreibbares Format gewandelt und in die benannte Datei geschrieben (Zeile 206). Das Hinzufügen einer neuen Adresse besteht darin, die Eingaben der Felder in die Liste ab aufzunehmen und sie als Zeichenkette in die Ansichtsliste zu schreiben. Hier die Methode: Listing 8.35: Die Methode zum Hinzufügen einer neuen Adresse 210 211 212 213 214 215 216 217 218 219 220 221
# Anzeige-String bilden text = '%(anrede)s %(vorname)s %(name)s, %(strasse)s, %(plz)s %(ort)s' %adr # In das Anzeigefeld schreiben self.anzeige.insert(END,text) # Eingabefelder zurücksetzen self.anrede.set('Herr') self.vorname.delete(0,END) self.name.delete(0,END) self.strasse.delete(0,END) self.plz.delete(0,END) self.ort.delete(0,END)
Da eine einzelne Adresse intern als Dictionary verwaltet wird, muss zunächst ein solches mit den Eingabedaten erzeugt werden (Zeile 213–218). Über die Methode get() der Widgets bzw. der Kontrollvariablen können die Felder ausgelesen werden. Dieses Dictionary muss dann der Liste ab hinzugefügt werden (Zeile 221). Mit den gleichen Daten wird anschließend eine Zeichenkette gebildet, die der Liste hinzugefügt wird (Zeile 223–226). Damit danach eine weitere Eingabe erfolgen kann, werden die Eingabefelder geleert (Zeile 229–234).
8.5.8
Generierung der Serienbriefe
Zum Schluss kommen nun Musterbrief und Adressbestand zusammen, um den Serienbrief zu erzeugen. Die zuständige Methode wird aus dem Menü aufgerufen: Listing 8.36: Die Methode zum Erzeugen des Serienbriefs 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
brief.close() del brief # Hinweis ausgeben info = mb.showinfo('Fertig', 'Die Briefe wurden erstellt')
Zunächst muss ein Verzeichnis gewählt werden können, in das die Briefe gespeichert werden. Auch dafür bietet das Modul tkFileDialog eine Methode an. Es ist die askdirectory()()()-Methode, die ein Fenster zur Auswahl eines Verzeichnisses zur Verfügung stellt (Zeile 239) und dessen absoluten Pfad zurückgibt. Es wird dann für jede Adresse die korrekte Anrede erzeugt (Zeile 242–245), ein Dateiname aus dem Nachnamen gebildet und dem Ordnerpfad angehängt (Zeile 248), eine Datei erzeugt (Zeile 250) und der Inhalt des Textfelds mit den ausgefüllten Platzhaltern in sie hineingeschrieben (Zeile 252). Ganz am Ende wird ein Hinweisfenster ausgegeben, das den Nutzer darüber informiert, das die Erstellung der Briefe abgeschlossen ist (Zeile 256) Für solche Hinweisfenster gibt es das Modul tkMessageBox(). Neben dem hier gezeigten Infofenster besitzt es noch Methoden für Fehlermeldungen (showerror()), Warnungen (showwarning()), Meldungen mit einem OK- und einem Abbrechen-Button (askokcancel()) und Meldungen mit einem Ja- und einem Nein-Button (askyesno()). Alle genannten Methoden haben zwei Parameter. Der erste steht für die Titelzeile des Meldungsfensters und der zweite für den Hinweistext.
240
9 Webentwicklung mit Python Python kann bei vielen Webhostern als Skriptsprache zur Generierung dynamischer Webseiten verwendet werden. Der verbreitetste Weg ist dabei sicherlich über das Common Gateway Interface (CGI), dem Standard für den Datenaustausch zwischen Webserver und anderer Software. Einige Provider bieten auch das Apache-Modul mod_python, das den Python-Interpreter in den Webserver integriert und die Ausführung von Skripten beschleunigt. Dynamische Webseiten trennen in der Regel zwischen der Darstellung einer Webseite, ihrem Inhalt und der Programmlogik. Für die Darstellung ist bei Webseiten – auch bei dynamisch erzeugten – HTML und CSS zuständig, die Inhalte werden in Datenspeichern – oftmals relationalen Datenbanken – abgelegt. Python kommt für die Entwicklung der Programmlogik ins Spiel, bei Webseiten ist dies oftmals das Auslesen von Inhalten aus einem Datenspeicher und das Einfügen in HTML-Code oder die Erzeugung von HTML-Code mit der Einbettung der ausgelesenen Daten.
9.1
CGI-Skripte
In der Regel können CGI-Skripte nur in speziell dafür vorgesehenen Verzeichnissen ausgeführt werden. Diese werden über die Webserver-Konfiguration bestimmt. CGI unterliegt strengen Sicherheitsbeschränkungen, damit diese Schnittstelle nicht für Angriffe auf den Server ausgenutzt werden kann. Der Webserver stellt dem ausführenden Programm oder Interpreter eine Laufzeitumgebung zur Verfügung, mit einer Reihe von Umgebungsvariablen. Diese enthalten z. B. Informationen über den Client (Browser-Version, Betriebssystem etc.), der zugreift. Des Weiteren gehören ein Eingabe- und ein Ausgabekanal dazu. Der Ausgabekanal des Servers wird dabei mit der Standardausgabe verknüpft, die Standardeingabe in der Regel mit etwaigen POST-Daten. In Bezug auf die Ausgabe bedeutet das, dass in CGI-Skripten, die mit Python geschrieben sind, print-Anweisungen an die Ausgabe des Webservers geleitet werden, der diese dann an den anfordernden Client sendet. CGI-Skripte haben einen gravierenden Nachteil: Jedes Mal, wenn ein solches Skript vom Browser aufgerufen wird, muss der Python-Interpreter gestartet werden. Das bedeutet Geschwindigkeitseinbußen, die bei hoch frequentierten Webseiten deutlich ins Gewicht fallen.
9 Webentwicklung mit Python
Die folgenden Beispiele beziehen sich auf eine CGI-Umgebung mit dem Apache Webserver auf einem lokalen Windows-Rechner. Das Standardverzeichnis für CGISkripte ist hier cgi-bin. So werden alle Aufrufe der Skripte in der Form http://localhost/cgi-bin/skript_name.py erfolgen. Bei Speicherplatz, der von Internet Providern zur Verfügung gestellt wird, kann die Lage des Verzeichnisses und damit der URL abweichen. Hinweis Informationen zum Apache Webserver erhält man auf der Webseite http://httpd.apache.org/. Eine teilweise deutschsprachige Dokumentation der aktuellen Version findet sich unter http://httpd.apache.org/ docs/2.2/. Es befinden sich auch auf der Buch-CD Apache-Versionen für die unterschiedlichen Zielplattformen. Wer schnell einen konfigurierten Apache-Webserver und dazu eine MySQL-Datenbank auf seinem Rechner installieren möchte, findet das sogenannte XAMPP-Paket (X = verschiedene Plattformen) unter http://www.apachefriends.org/de/xampp.html. Dieses Paket enthält allerdings auch PHP.
9.1.1
Aufbau eines CGI-Skripts mit Python
Ein CGI-Skript muss immer mit einer Anweisung an das Betriebssystem beginnen, welcher Interpreter benutzt werden soll. Es muss also eine Magic Line in der ersten Zeile des Skripts notiert werden. Wenn das Skript auf einem Linux-Server läuft, muss die Zeile z. B. so aussehen: #!/usr/bin/python
Es kann aber durchaus ein anderer Pfad sein, je nachdem, welche Linux-Distribution der Internet Provider einsetzt. Im Zweifel sollte dieser darüber Auskunft geben können. Auch unter Windows wird diese Zeile benötigt. Die Dateiendung reicht in der CGIUmgebung für die Zuordnung des Interpreters nicht aus. Es sind zwei Formen für die Magic Line möglich: #!c:\Python25\python.exe #!/Python25/python.exe
Die tatsächliche Zeile ist natürlich auch hier vom tatsächlichen Installationsort des Python-Interpreters abhängig.
242
9.1 CGI-Skripte
Es sollte dann eine Zeile folgen, die die Zeichenkodierung des Skripts definiert. # -*- coding: utf-8 -*-
Diese Zeile z. B. stellt die Kodierung auf UTF-8 ein. Damit ist es möglich, UnicodeZeichen direkt im Quelltext zu verwenden. Mit der folgenden Zeile # -*- coding: iso-8859-15 -*-
wird die Kodierung auf einen erweiterten ASCII-Zeichensatz eingestellt, der deutsche Umlaute und auch das _-Zeichen enthält. Die erste Ausgabezeile muss immer Header-Informationen über den MIME-Typ an den Client senden. Dieser teilt dem Client mit, welche Art Daten gesendet wurden. Hier sind es HTML-Seiten, deren Typ mit text/html bezeichnet wird. Die komplette Header-Zeile wird folgendermaßen notiert: print 'Content-Type: text/html\n'
Wichtig ist, dass hier eine leere Zeile folgt. Dies kann man wie im Beispiel durch Einfügung eines Umbruchs am Ende des Strings erreichen oder durch eine zweite leere print-Anweisung. Fehlt die Leerzeile, quittiert der Webserver dies mit einer Fehlermeldung. Man kann hier auch eine Zeichenkodierung für die HTML-Seite mitsenden, die der Browser dann entsprechend umsetzt: print "Content-Type: text/html;charset=utf-8;\n"
Die Angabe wird mit einem Semikolon getrennt hinter dem MIME-Typ notiert. Wenn die Webseite eine bestimmte Zeichenkodierung für eine korrekte Darstellung benötigt, ist es empfehlenswert, hier die Angabe zu machen. Danach kann man beginnen, die HTML-Seite aufzubauen. Man muss dabei folgende Regel beachten: Alles, was an den Client geschickt werden soll, muss mit der printAnweisung notiert werden. Hier ein erstes einfaches Skript: Listing 9.1: Python-CGI-Skript 01 02 03 04 05 06 07 08 09
Wie man sieht, wird vor allem HTML-Code über die print-Anweisung ausgegeben. Eingefügt werden zwei Zeichenketten, die das Modul sys liefert (Zeile 14 und 17) (siehe Abschnitt 7.1). Für solche Ausgaben bietet es sich an, mehrzeilige Zeichenketten zu verwenden. Der HTML-Code ist dadurch gut lesbar und damit leichter zu editieren. Dass die import-Anweisung (Zeile 04) vor der Header-Zeile (Zeile 06) notiert ist, löst keinen Fehler aus. Die Header-Zeile muss die erste Ausgabezeile sein, also die erste Zeile, die eine print-Anweisung enthält. Wenn die Datei mit info.html benannt ist, kann man im Browser die Adresse http://localhost/cgi-bin/info.html eingeben und erhält folgende Seite:
Abbildung 9.1:
244
Ausgelieferte Webseite
9.1 CGI-Skripte
Achten Sie darauf, dass unter Linux die Rechte für CGI-Skripte so gesetzt sind, dass sie ausführbar sind, andernfalls kann das Skript den Interpreter nicht aufrufen und es kommt zu einer Fehlermeldung. Man kann Python-CGI-Skripte durchaus mit der Endung .html versehen. Wie bereits erwähnt, spielt die Dateiendung für die Ausführung des Skripts keine Rolle, sondern die Magic Line im Skript bestimmt den Interpreter. Verwendet man die Endung .html, hat das den Vorteil, dass der Nutzer eine gewohnte Adresse angezeigt bekommt. Auch Suchmaschinen nehmen die Seite so gegebenenfalls eher als das wahr, was sie zumindest auf Client-Seite ist: eine einfache HTML-Seite.
9.1.2
Inhalte dynamisch einfügen
Bereits das Beispiel aus dem vorigen Abschnitt hat das Grundprinzip des dynamischen Einfügens von Inhalten gezeigt: Über Python-Logik werden Daten gelesen und in eine HTML-Struktur eingefügt. Dabei kommen die Daten in der Regel aus externen Datenquellen, z. B. einer Datenbank. Aber auch einfache Textfiles oder mit pickel.dump() codierte Daten können die Quelle sein. Im folgenden Beispiel wird eine Datei ausgelesen, die ein mit pickle.dump() eingelegtes Dictionary als Datenquelle benutzt. Das Dictionary hat die Struktur {'headline': inhalt, 'text': inhalt}
Wichtig ist, dass die Datei, die die Daten enthält, ebenfalls in dem Verzeichnis abgelegt ist, in dem sich das Skript befindet (oder einem Unterverzeichnis davon). Aus Sicherheitsgründen dürfen CGI-Skripte keine Dateien außerhalb des CGI-Verzeichnisses auslesen. Das Beispiel bezieht sich also auf eine Datei mit dem Namen inhalte.dmp im Verzeichnis cgi-bin der Apache-Installation. Die Skript-Datei erhält den Namen seite.html. Listing 9.2: Seite mit Inhalten aus einer Datei 01 02 03 04 05 06 07 08 09 10 11 12 13 14
In gewohnter Weise wird die Datei inhalte.dmp ausgelesen und der Inhalt in ein Dictionary konvertiert (Zeile 08 und 09). Dieses wird dann genutzt, um die Platzhalter (Zeile 15 und 16) mit Inhalt zu füllen (Zeile 19). Wie man an diesem Beispiel gut sehen kann, ist es auch möglich, Python-Logik zwischen zwei print-Anweisungen zu setzen. Auch die HTML-Struktur kann von Python-Logik durchsetzt sein, wenn dies erforderlich ist. Man muss dann den HTMLCode auf mehrere print-Anweisungen verteilen. Das obige Beispiel erzeugt folgende Webseite:
Abbildung 9.2: Dynamisch erzeugte Webseite
9.1.3
Den Datenspeicher bedienen
Es ist natürlich die Frage, wie die Inhalte in den Datenspeicher kommen. Im Beispiel des vorigen Abschnitts wurde eine vorher über die IDLE erstellte Datei verwendet. In einem System, das vom Webseiteninhaber verwaltet wird, sollte es aber eine einfache Möglichkeit geben, dass dieser die Inhalte auf seiner Webseite selbst eingibt und verändert. HTML stellt für die Dateneingabe Formularelemente zur Verfügung. Sollen die eingegebenen Daten weiterverarbeitet werden, z. B. in einer Datenbank gespeichert werden, so benötigt man ein auswertendes Skript, das beim action-Attribut des form-Tags angegeben wird. Dieses Skript kann natürlich auch ein Python-Skript sein, dass die Formulardaten entgegennimmt und weiterverarbeitet. Damit stehen dann alle Möglichkeiten der Datenspeicherung offen.
246
9.1 CGI-Skripte
Wenn man das Beispiel aus dem vorigen Abschnitt um eine Dateneingabe erweitern will, benötigt man also zunächst ein Eingabeformular. Dieses wird in einer Datei mit dem Namen formular.html realisiert: Listing 9.3: Eingabeformular für die Seiteninhalte 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
Vom Prinzip her unterscheidet sich diese Datei nicht von der Ausgabe aus dem letzten Abschnitt. Es wird hier nur das Öffnen der Datei und das Auslesen des Dictionary in einen try-Block gesetzt (Zeile 08–10). Sollte die Datei noch nicht vorhanden sein, weil noch keine Inhalte angelegt wurden, wird im except-Block ein Dictionary ohne Werte erzeugt (Zeile 12). Es wird in den Feldern also entweder vorhandener Inhalt angezeigt, sodass man ihn bearbeiten kann, oder sie bleiben leer. Das Formular erhält für sein action-Attribut den Dateinamen eines Python-Skripts, das aufgerufen wird, wenn man den Submit-Button betätigt (Zeile 19). Die Daten aus dem Dictionary werden als Werte der Eingabefelder eingetragen (Zeile 23 und 27). Diese sind mit ihrem Attribut name genauso benannt wie die Schlüssel im Dictionary. Dies macht die spätere Verarbeitung der Eingabedaten einfacher. Die Eingabeseite kann im Browser dann mit der Adresse http://localhost/cgi-bin/formular. html aufgerufen werden und sieht folgendermaßen aus:
Abbildung 9.3: Das Eingabeformular Das Skript, das nun mit den Eingabedaten umgehen soll, muss das Modul cgi importieren. Dieses Modul besitzt die Klasse FieldStorage, die ein Objekt erzeugt, das die Formulardaten enthält. Über dieses Objekt kann im Skript auf die Daten zugegriffen werden. Was kompliziert klingt, ist eigentlich ganz einfach: Listing 9.4: Das Skript für die Datenspeicherung 01 #!/Python25/python.exe 02 # -*- coding: iso-8859-15 -*03
Für die Speicherung der Daten ist es wichtig, dass sie zunächst in ein Dictionary umgewandelt werden. Das Objekt formular (Zeile 07) besitzt die Methode keys(), die eine Liste aller enthaltenen Namen zurückgibt. Namen meint hier die Namen der Formularfelder des Eingabeformulars. Da diese entsprechend der Schlüssel des Ziel-Dictionary gewählt wurden, kann man dieses sehr einfach mit einer for-Schleife aufbauen (Zeile 11 und 12). Um an den Wert der Felder zu gelangen, verwendet man die Methode getvalue(). Diese verlangt als Parameter eben den Namen eines Formularfelds. Anschließend muss das Dictionary nur noch auf die bekannte Weise gespeichert werden (Zeile 14 und 15). Als Reaktion gibt das Skript nur eine Meldung darüber aus, dass die Daten gespeichert wurden. Mit zwei Links, einmal zur Eingabe- und einmal zur Ausgabe-Seite (Zeile 25), kann man nun zwischen Seiten navigieren. Geht man direkt auf die Ausgabeseite, kann man das Ergebnis der Textänderung umgehend in Augenschein nehmen. Die nachfolgende Grafik gibt einen Überblick über den Zusammenhang aller beteiligten Skripte:
249
9 Webentwicklung mit Python
Abbildung 9.4: Die Struktur der CGI-Skripte Neben den hier verwendeten Methoden von FieldStorage bietet die Klasse noch einige weitere. Hier eine Übersicht: Methode
Erläuterung
keys()
Gibt eine Liste der im Formular enthalten Namen zurück.
has_key(name)
Prüft, ob der angegebene Name enthalten ist, und gibt TRUE zurück, andernfalls FALSE.
getvalue(name)
Gibt den Wert des angegebenen Formularelements zurück.
getlist(name)
Gibt den Wert des angegebenen Formularelements immer als Liste zurück. Existiert das Element nicht, wird eine leere Liste zurückgegeben. Formularelemente, die eine Mehrfachauswahl zulassen, werden als Listen ausgewertet. Wenn aber nur ein Wert gewählt wurde, erhält man einen Einzelwert. Mit dieser Methode kann man dafür sorgen, dass man in jedem Fall eine Liste erhält. Dadurch kann man in der Auswertung eine Einzel- wie eine Mehrfachauswahl behandeln.
getfirst(name)
Gibt bei einem Element, das als Liste ausgewertet wurde, nur den ersten Wert zurück.
Tabelle 9.1: Methoden der Klasse FieldStorage
9.1.4
Reine Skriptverarbeitung
In vielen Fällen benötigt man ein CGI-Skript, das nur Programmlogik ausführt, aber keinen HTML-Code generiert. Denkt man zum Beispiel an einen Chat, der von einem Skript aktualisiert wird, so wäre es unsinnig, in das Aktualisierungsskript noch mal
250
9.1 CGI-Skripte
den ganzen HTML-Code für den Chat einzubinden. Auch im Sinne einer Trennung von Layout und Inhalt ist es wünschenswert, reine Skriptdateien zu haben, die keinen HTML-Code enthalten. Allerdings kann man in ein CGI-Skript nicht einfach nur Python-Code schreiben. Der Webserver verlangt etwas, was er an den Client senden kann. Man kann dieses Ansinnen bedienen, indem man eine zusätzliche Header-Zeile einfügt, die einen Redirect auslöst: #!/Python25/python.exe # -*- coding: iso-8859-15 -*# [hier wird die Programmlogik notiert]
Wird ein solches Skript ausgeführt, wird auf die bei der Location-Zeile angegebene Webseite weitergeleitet. Vorher aber wurde die Programmlogik ausgeführt, z. B. das Speichern in einer Datenbank oder das Versenden einer E-Mail. Man kann vorher auch eine Statuszeile ausgeben, z. B.: print "Status: 301 moved"
Diese Anweisung teilt dem Browser mit, dass die angeforderte Seite dauerhaft unter einer anderen Adresse zu finden ist. Es ist allerdings besser, auf diese Zeile zu verzichten und bei Location eine relative Adresse anzugeben. Dies veranlasst den Webserver sofort, das neue Dokument auszuliefern.
9.1.5
Fehlerbehandlung
Der Webserver kennt kein Python. Ihm ist es letztlich egal, in welcher Sprache das CGI-Skript geschrieben ist. Was ihm allerdings nicht egal ist, sind Fehler in der Skript-Verarbeitung. Je nach Sprache bekommt der Server dann keine oder nur bruchstückhafte Daten zur Auslieferung an den Client. Bei Python bekommt er keine Daten und reagiert mit einem Internal Server Error. Er kann aber kein Traceback ausgeben, das einem wertvolle Informationen über die Fehlerursache und ihre Position im Skript gibt. Um dennoch Fehlermeldungen über den Browser zu erhalten, kann man das Modul cgitb (cgi-Traceback) importieren und dessen Methode enable() ausführen lassen. Dieses Modul fängt im Skript auftretende Fehler ab und erzeugt eine HTML-Seite mit dem Traceback, die es an den Webserver weitergibt. Dieser erhält dann wieder Daten, die er ausliefern kann, und reagiert nicht mit der oben genannten Meldung. Ein Beispiel:
if test == 'fehler': print ' es geht nicht.' print "Content-Type: text/html;charset=iso-8859-15;\n" print """ Fehlertest
provoziert einen Fehler
Im Skript ist die Umleitung des Traceback an den Webserver aktiviert (Zeile 04 und 05). Da die Variable test nicht definiert ist (Zeile 07), kommt es zu folgendem Fehler:
Abbildung 9.5: Fehlerausgabe im Browser
252
9.2 Python Server Pages mit mod_python
Man kann cgitb auch dazu bewegen, die Fehlerausgabe in Dateien umzulenken. Die Methode enable() hat dafür entsprechende Parameter: disable – bestimmt, ob die Ausgabe an den Browser erfolgen soll. Der Standardwert ist 1, es wird also keine Ausgabe an den Browser gemacht. logdir – hier kann der Pfad eines Verzeichnisses angegeben werden, in das die Log-
Dateien geschrieben werden. context – bestimmt, wie viele Zeilen um die fehlerträchtige Zeile herum angezeigt werden sollen. Der Standardwert ist 5. format – bestimmt das Ausgabeformat. Der Standardwert ist HTML. Möglich ist ansonsten nur noch plain/text, das für die Ausgabe in eine Datei vorzuziehen ist,.
9.2
Python Server Pages mit mod_python
Eine andere Möglichkeit, Seiteninhalte mit Python dynamisch zu erzeugen, sind die Python Server Pages (PSP). Dies ist ein Konzept, das an PHP, aber auch an Java Server Pages (JSP) angelehnt ist. Hier werden Skriptbefehle in den HTML-Code eingebettet und für den Interpreter durch besondere Zeichen kenntlich gemacht. Für alle, die PHP gewohnt sind, dürfte dies eine vertraute Vorgehensweise sein. Für all jene, die besonderen Wert auf die Trennung von Layout und Programmlogik legen, sind PSP sicherlich nicht die erste Wahl. Dennoch haben PSP einige Vorteile gegenüber CGI-Skripten. Vor allem ist es die Geschwindigkeit der Ausführung, die auf Seiten von PSP deutlich besser ist als bei CGI. PSP basiert auf dem Apache-Modul mod_python, das ähnlich funktioniert wie die PHP- oder Perl-Module des Webservers. Das Modul schafft eine direkte Verbindung zum Python-Interpreter, sodass die Skriptausführung quasi in den Server verlagert wird. Ein Interpreteraufruf mit jedem Skript entfällt. Es gibt einige interessante Frameworks, die ebenfalls über mod_python laufen (können) und durch Template-Systeme eine gute Trennung von Logik und Layout gewährleisten. Es ist also durchaus angebracht, einen Blick auf mod_python und PSP zu werfen.
9.2.1
Installation und Konfiguration
Man muss zunächst das Modul mod_python von der Webseite http://httpd.apache. org/modules/python-download.cgi herunterladen. Man enthält ein Paket mit den Quellen, das unter Linux zu installieren ist. Für eine Windows-Version muss man dem Link Win 32 Binaries folgen. Auf der dann erscheinenden Seite erhält man einen Installer für die aktuelle Version 3.3.1. Es ist unbedingt darauf zu achten, dass man den richtigen Installer für die richtige Python-Version passend für die richtige Apache-Version herunterlädt.
253
9 Webentwicklung mit Python
Unter Linux führt man zunächst $ ./configure
und dann $ make
aus. Der letzte Schritt muss als root ausgeführt werden, also: $ su '# make install
Hinweis Nähere Informationen zur Installation, insbesondere zu Optionen für configure finden sich im Manual zu mod_python unter http://www. modpython.org/live/current/doc-html/modpython.html
Unter Windows muss nur der Installer aufgerufen und seinen Anweisungen gefolgt werden. Er platziert alle Software-Bestandteile in die richtigen Verzeichnisse. Anschließend muss in der Apache-Konfiguration das Modul zum Laden eingetragen werden. Je nach System oder Linux-Distribution muss in die Datei httpd.conf oder apache.conf folgende Zeile eingetragen werden: LoadModule python_module modules/mod_python.so
Auch hier kann es wieder Abweichungen beim Pfad zur Moduldatei geben, je nachdem, auf welchem System man arbeitet. Unter Windows kann es notwendig sein, Python zur Umgebungsvariablen Path hinzuzufügen (siehe Abschnitt 1.2), damit das Modul geladen werden kann. Ebenfalls in die Konfigurationsdatei muss dann folgende Direktive geschrieben werden: AddHandler mod_python .psp .psp_ PythonHandler mod_python.psp PythonDebug On
Das Verzeichnis, das in der ersten Zeile angegeben wird, sollte ein Unterverzeichnis von htdocs sein, dem Standardverzeichnis von Apache für Webseiten. Mit der Anweisung AddHandler wird der Server veranlasst, alle Dateien mit Endung .psp von
254
9.2 Python Server Pages mit mod_python
mod_python verarbeiten zu lassen. Die Direktive PythonHandler teilt mod_python mit, in welchem Modus es für dieses Verzeichnis arbeiten soll, in diesem Fall wollen wir den PSP-Modus. Das Debugging einzuschalten, ist für die Entwicklung eine gute Idee, da es Fehlermeldungen liefert, die für die Fehlersuche wertvoll sind. Für Seiten, die tatsächlich öffentlich zugänglich sind, sollte man die Zeile entfernen oder das Debugging ausschalten (off), weil es erhebliche Performance-Einbußen mit sich bringt. Diese Direktiven können auch in die htacess-Datei des Verzeichnisses notiert werden. Die erste und letzte Zeile sind dann nicht notwendig. AddHandler mod_python .psp .psp_ PythonHandler mod_python.psp PythonDebug On
Wenn alles so weit konfiguriert ist, muss Apache neu gestartet werden. Jetzt kann ein erstes Testskript in dem konfigurierten Verzeichnis angelegt werden. Für die Beispiele ist es das Verzeichnis psp im htdocs-Verzeichnis von Apache. Alle Beispiel-Skripte können dann mit der Adresse http://localhost/psp/skript_datei.psp aufgerufen werden. Um einen direkten Vergleich zur CGI-Programmierung zu haben, sollen noch mal die Beispiele aus Abschnitt 9.1, jetzt mit PSP, aufgegriffen werden. Zunächst das Testskript, jetzt mit dem Dateinamen info.psp: Listing 9.6: Die Infoseite mit PSP 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
<% import sys %>
Info
<%= sys.version %>
<%= sys.copyright %>
Wie man sehen kann, fehlen die Magic Line und die Header-Zeile und auch sonstige print-Anweisungen. Der Python-Code wird direkt in die HTML-Struktur eingebettet, kenntlich gemacht durch die Zeichenfolge <% und %>. Der folgende Abschnitt erläutert diese Zeichen und einige wenige Syntaxregeln für PSP.
255
9 Webentwicklung mit Python
9.2.2
Sicherheitsaspekte
Es werden bei PSP zwar auch nur einzelne Verzeichnisse definiert, in denen die Ausführung von Python-Code möglich ist (siehe Abschnitt 9.2.1), trotzdem muss man wie bei anderen in den Webserver integrierten Sprachen einige Sicherheitsaspekte bedenken: Sensible Daten sollten nicht im Root-Verzeichnis der Website abgelegt werden. Der Python-Interpreter von mod_python wird vom gleichen Benutzer ausgeführt wie
der Apache-Webserver. Das bedeutet, das eventuell andere integrierte Programme Zugriff auf Skripte und Seiten haben können. Man sollte keinen Gebrauch von der Umgebungsvariablen PATH machen, weil sie
leicht von anderen Anwendungen geändert werden könnte. Muss man andere Programme auf dem Server aufrufen, sollte man immer einen absoluten Pfad verwenden. Wenn man im Entwicklungsstadium den Debug-Modus angeschaltet hat, sollte
man bedenken, dass in den Fehlermeldungen unter Umständen sensible Daten angezeigt werden (z. B. Pfade zu wichtigen Dateien). Für eine veröffentlichte Webseite sollte der Debug-Modus immer ausgeschaltet werden. Entwickelt man auf einem öffentlich zugänglichen Server, sollte man Gebrauch von try-except-Anweisungen machen, damit man selbst die Ausgaben im Fehlerfall kontrollieren kann. Wenn der Apache-Webserver Schreibrechte für die Verzeichnisse der Websites
hat, dann können kompilierte Python-Dateien (.pyc) angelegt werden. Diese sollte man mit einer entsprechenden Direktive (s. u.) vor Zugriffen schützen. Hat man bei der AddHandler-Direktive auch die Dateiendungen .html und .htm angegeben, sollte man auch den Zugriff auf Skriptdateien (.py) unterbinden. Die entsprechende Apache-Direktive sieht folgendermaßen aus: deny from all
Hinweis Spezifische Sicherheitsaspekte der einzelnen Modi von mod_python finden sich auf der Webseite http://wiki.apache.org/mod_python/SecurityConsiderations.
256
9.2 Python Server Pages mit mod_python
9.2.3
PSP-Schreibweisen
PSP setzt Textdokumente (und darin sind HTML-Dokumente eingeschlossen), die Python-Anweisungen enthalten, in reinen Python-Code um. Die Python-Anweisungen werden in speziell ausgezeichnete Klammern gesetzt. Eine minimalistische Seite wie <% import sys %>
<%=sys.version%>
wird dabei so umgesetzt: req.write("""""") import sys req.write("""
""") req.write(sys.version) req.write("""
""")
Dabei erzeugt die Methode write() des Request-Objekts (req) die Ausgabe an den Client. Man kann übrigens genau sehen, wie der Parser eine Seite in reinen PythonCode umsetzt, wenn man die Seite mit der Endung .psp_ aufruft. Man erhält dann eine Ansicht, in der der geparste Python-Code und der PSP-Code nebeneinander gestellt sind. In das HTML-Dokument (Textdokument) wird Python-Code folgendermaßen eingebettet: <% (merhzeiliger) Code %>
Python-Ausdrücke (z. B. Variablennamen, Funktionsaufrufe) werden hingegen so eingebettet: <%= Python-Ausdruck %>
Einen Kommentar, der beim Rendern der Seite nicht ausgegeben werden soll, schließt man wie folgt ein: <% -- Kommentar (auch mehrzeilig) --%>
257
9 Webentwicklung mit Python
Schließlich gibt es noch eine Syntax für sogenannte Direktiven (spezielle PSP-Anweisungen), von denen es aber derzeit nur eine gibt, die das Einfügen von anderen Dateien erlaubt: <%@ include file="/pfad/zur/datei"%>
Es ist möglich, die Ausgabe von HTML-Bereichen unter Bedingungen zu stellen. Dabei geht man folgendermaßen vor: <% if test == 'erfolgreich': %>
Alles ist gut
<% else: %>
Das war noch nichts
<% %>
Nachfolgendes HTML
Der if-Block wird in die entsprechenden Klammern gesetzt und anschließend wird das HTML notiert, das angezeigt werden soll. Das else-Statement wird erneut in entsprechende Klammern gesetzt und dann das alternative HTML. Es entsteht allerdings ein Problem mit der Einrückung, die ja bedeutsam für die Identifizierung von Code-Blöcken ist. Der else-Block, zu dem auch das HTML gehört (der Parser setzt es ja als reg.write("""
...
""")um), muss explizit durch ein weiteres Klammernpaar beendet werden, damit nachfolgendes HTML einen neuen Block darstellt und nicht unter die Bedingung fällt.
9.2.4
Daten auslesen und Formulardaten speichern
Um noch einmal einen Eindruck von der Funktionsweise von PSP zu geben, sollen hier die Beispiele aus dem Abschnitt über CGI-Skripte nochmals aufgegriffen und mit PSP umgesetzt werden. Zunächst das Auslesen von Inhalten aus der Datei inhalte.dmp und das Einfügen der Daten in die HTML-Struktur: Listing 9.7: Auslesen und Einfügen 01 02 03 04 05 06 07
Erste Seite <% from pickle import load import sys dat_obj = file(r'htdocs\tests\inhalte.dmp','r')
258
9.2 Python Server Pages mit mod_python
08 inhalt = load(dat_obj) 09 %> 10
<%= inhalt['headline'] %>
11
<%= inhalt['text'] %>
12 13
Das Auslesen und Einfügen der Daten erfolgt in der gleichen Weise wie auch beim CGI-Skript (Zeile 05–08). Da der HTML-Code aber hier direkt ausgegeben wird, erfolgt die Ausgabe der Daten mit Zugriffen auf das Dictionary inhalt (Zeile 10 und 11). Ähnlich verhält es sich mit dem Formular, das zur Dateneingabe dient: Listing 9.8: Das Formular mit PSP 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
Wieder ist der Code zum Auslesen der Daten identisch mit dem CGI-Skript. Was bei der Datenausgabe auffällt, ist, dass PSP-Ausdrücke auch in Anführungszeichen stehen können (Zeile 20). Trotzdem werden sie korrekt ausgewertet. Schließlich folgt die Datei, die das Speichern der Formulardaten übernimmt: Listing 9.9: Formulardaten speichern mit PSP 01 02 03 04 05 06 07 08 09 10 11 12 13 14
Speichern <% from pickle import dump speicher_daten = {} for item in form.keys(): speicher_daten[item] = form[item]
Im Prinzip läuft auch dieser Vorgang so ab wie im CGI-Skript. Allein der Zugriff auf die Formulardaten ist ein anderer. Unter PSP steht die globale Variable form zur Verfügung, die alle Formulardaten enthält. Es ist ein Dictionary, dessen Schlüssel den Namen der Formularelemente entsprechen. Die Werte der Elemente sind entsprechend zugeordnet. Der Zugriff auf einen Wert eines Formularelements erfolgt also in der Form: form[name_des_elements]
Oder man verwendet die Methode get() des Dictionary-Objekts (siehe Abschnitt 3.2.5).
260
9.2 Python Server Pages mit mod_python
9.2.5
PSP als Template-Sprache
Wie bereits eingangs dieses Abschnitts erwähnt, führt die oben beschriebene Art der Verwendung von PSP zu Code, in dem Programmlogik und Layout (HTML-Struktur) stark miteinander vermischt sind. Man kann aber PSP auch so verwenden, dass es »nur« noch als Template-Sprache dient und die Programmlogik in Python-Skripten ausgelagert ist. Hinweis Als Template wird HTML-Code bezeichnet, der das Layout einer Webseite oder eines Teils einer Webseite definiert. Für den Inhalt werden Platzhalter vorgesehen, die beim Aufruf der Seite spezifisch gefüllt werden. So kann man etwa für eine Nachricht ein solches Template mit z. B. Überschrift und Anleser einmalig anlegen und es so häufig verwenden, wie man Nachrichten ausgeben möchte. Voraussetzung dafür ist eine andere Konfiguration des Webservers. Hier das Beispiel für das Verzeichnis, in dem im Folgenden alle Skripte abgelegt werden: SetHandler mod_python PythonHandler mod_python.publisher PythonDebug On
Die Direktive SetHandler teilt Apache mit, dass alle Dateien in dem angegebenen Verzeichnis von mod_python verarbeitet werden sollen, unabhängig von deren Endungen. Entscheidend ist aber die folgende Zeile mit dem Modus, in dem mod_python für dieses Verzeichnis auf publisher gesetzt wird. In diesem Modus können Funktionen und Variablen in einem Python-Skript über den URL aufgerufen werden. Was zunächst seltsam klingen mag, wird sich im weiteren Verlauf als ein Feature herausstellen, das es erlaubt, Webseiten so aufzubauen, dass die HTML-Struktur in Templates ausgelagert wird. In diese Templates wird dynamisch der spezifische Inhalt eingefügt und die Programmlogik ist davon vollkommen getrennt. Das Prinzip soll an einem Beispiel erläutert werden, das alles schmückende Beiwerk beiseite lässt, um sich auf das Grundprinzip zu konzentrieren. Es wird eine Webseite aufgebaut, die von der HTML-Struktur recht einfach aufgebaut ist: eine Tabelle mit nur einer Zeile, die aus zwei Zellen besteht. Die linke Zelle soll ein Menü beherbergen, in der rechten Zelle soll der seitenspezifische Inhalt erscheinen. Diese HTML-Struktur wird für das Projekt nur ein einziges Mal angelegt. Jede Seite nutzt diese Struktur, um ihre Inhalte darin einzubringen. Das ist die klassische Template-Struktur. In diesem Projekt ist die HTML-Struktur in drei Templates zerlegt: der äußere Rahmen mit den HTML-Grundkomponenten head und body sowie der Tabelle und dann je ein Template für das Menü und für die Seiteninhalte. Alle Templates sind in einem Unterverzeichnis mit dem Namen tmpl abgelegt.
261
9 Webentwicklung mit Python
Hier zunächst das Template, das den Rahmen bildet. Es hat den Dateinamen container.tmpl: Listing 9.10: Das container-Template 01 02 03 seitentitel 04 05 06
07
08
09
10
11
12
<%= menu %>
13
<%= content %>
14
15 16
Das ist eine einfache HTML-Struktur, mit der oben erwähnten Tabelle. Es gibt allerdings zwei PSP-Anweisungen (Zeile 12 und 13). Hier werden die Templates für das Menu (menu) und für den Inhalt (content) eingefügt. Das Template für die Inhalte hat den Dateinamen content.tmpl und folgenden Inhalt:
<%= page_content %>
Da es hier nicht um ausgetüftelte Inhalte gehen soll, ist die Struktur dieses Template besonders einfach. Lediglich der Inhalt einer Variablen mit dem Namen page_content soll eingefügt werden. Schließlich ist noch das Template für das Menü zu betrachten, das in der Datei menu.tmpl untergebracht ist: Listing 9.11: Das menu-Template 01 02 03 04 05 06
Hier wird eine Schleife durchlaufen und mit jedem Durchlauf ein Link erzeugt (Zeile 04). Im Zusammenhang mit den im Folgenden besprochenen Skripten wird deutlicher, was hier passiert.
262
9.2 Python Server Pages mit mod_python
Alle Skripte sind im Verzeichnis site abgelegt. Es gibt ein Skript, das Funktionen für den Aufruf der Templates enthält, und eines, das Funktionen für die Seitenaufrufe enthält. Die Funktionen für die Seitenaufrufe werden über den Browser ausgeführt. Das Skript mit diesen Funktionen hat den Dateinamen main.py, eine Funktion für den Seitenaufruf trägt z. B. den Namen seite1. Ein Aufruf dieser Funktion erfolgt dann mit dieser Adresse: http://localhost/site/main/seite1 Die Dateiendung .py, die die Skriptdatei eigentlich hat, kann weggelassen werden. Es ist ein Teil der besonderen Funktionalität von mod_python, dass es Skriptdateien ohne die Dateiendung erkennt. Das hat den Vorteil, dass die Webadresse wie eine normale Adresse zu einer statischen Seite aufgebaut ist. Für die Indexierung von Suchmaschinen ist das zum Beispiel nicht unwichtig. Das Skript für die Seitenaufrufe hat folgenden Quelltext: Listing 9.12: Das Skript main.py 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18
# -*- coding: utf-8 -*from container import * #Liste mit Seitennamen MENUDATA =['seite1','seite2'] # Funktionen, die die jeweiligen Seiten erzeugen def seite1(req): my_content = content(req,{'page_content':'inhalt für seite1'}) my_menu = menu(req,MENUDATA) vars = {'content':my_content,'menu':my_menu} return page(req,vars) def seite2(req): my_content = content(req,{'page_content':'inhalt für seite2'}) my_menu = menu(req,MENUDATA) vars = {'content':my_content,'menu':my_menu} return page(req,vars)
Es importiert zunächst alle Namen vom zweiten Skript container.py (Zeile 02). Das ist notwendig, weil von diesem Skript Funktionen aufgerufen werden. Es definiert dann eine Konstante mit dem Namen MENUDATA, die für dem Menüaufbau notwendig ist. Für jede Seite ist eine Funktion angelegt. Der Aufbau der Funktionen ist jeweils der gleiche. Es wird die Funktion für das Inhalts-Template aufgerufen (Zeile 09) und Gleiches geschieht für das Menü-Template (Zeile 10). Beide Templates werden in ein Dictionary eingefügt (Zeile 11), das an den Aufruf der Funktion für das Rahmen-Template übergeben wird (Zeile 12). Das heißt, die Templates für den Inhalt und das Menü werden einfach per Variablenübergabe an das Rahmen-Template geleitet. Die-
263
9 Webentwicklung mit Python
ses fügt die HTML-Struktur dieser Templates in seine eigene ein und eine komplette Seite entsteht. Über die return-Anweisung wird diese an den Webserver weitergegeben, der sie an den Browser sendet.. Für das komplette Verständnis des Prozesses ist ein Blick auf das Skript notwendig, das die Funktionen enthält, die die Templates aufrufen. Diese Funktionen sind in der Datei container.py notiert: Listing 9.13: Das Skript container.py 01 from mod_python import psp 02 03 def page(req,vars): 04 template = psp.PSP(req, filename='tmpl/container.tmpl',vars=vars) 05 return template 06 07 def content(req,vars): 08 template = psp.PSP(req, filename='tmpl/content.tmpl',vars=vars) 09 return template 10 11 def menu(req,menu_data): 12 vars = {'pages':menu_data} 13 template = psp.PSP(req, filename='tmpl/menu.tmpl',vars=vars) 14 return template
Das Skript wird von main.py importiert. Alle Funktionen stehen somit dort zur Verfügung. Es importiert von dem Modul mod_python die Klasse psp. Damit kann es die Templates aufrufen (Zeile 04, 08 und 12). An die Templates werden die Variablenwerte übergeben, die für die mit PSP notierten Platzhalter in den Templates eingefügt werden. Für das Rahmen-Template (Funktion page, Zeile 03–05) sind dies wiederum Templates. Das heißt, es werden dort die HTML-Strukturen der Templates menu.tmpl und content.tmpl eingefügt. In diesen Templates sind es Daten, die in das Template eingefügt werden. Das Menü-Template erhält z. B. die Liste MENUDATA, die im Skript main.py definiert ist. Im Template selbst wird über diese Liste iteriert und für jedes Listenelement ein Link erstellt. Die folgende Abbildung zeigt den Aufbau der Template-Struktur in einem Schema. Sie verdeutlicht, dass die einzelne Seite nun aus mehreren Versatzstücken zusammengebaut wird. Für das Inhalts-Template ist es ein Wert, der im Moment noch fest in ein Dictionary eingetragen ist. Es wurde in diesem Abschnitt das Grundprinzip der Verwendung von Templates erläutert. Üblich ist es, die Seiteninhalte in einer relationalen Datenbank abzulegen und von dort wieder aufzurufen. Mit der in diesem Abschnitt erarbeiteten Grundstruktur einer Webanwendung ist der Schritt dahin relativ klein. Er wird im folgenden Abschnitt gemacht.
264
9.2 Python Server Pages mit mod_python
Abbildung 9.6: Schema der Template-Struktur Zum Abschluss dieses Abschnitts soll noch das Ergebnis der Skripte, wie es der Browser darstellt, gezeigt werden.
Abbildung 9.7: Die Basis-Webanwendung im Browser
265
9 Webentwicklung mit Python
9.3
Anbindung an relationale Datenbanken
Viele Provider bieten als Datenbanksystem MySQL an (http://www.mysql.org/). Und Python kann natürlich mit dieser Datenbank kommunizieren. Realisiert wird diese Kommunikation über einen sogenannten Adapter, ein Zusatzmodul, das installiert werden muss. Für die Zusammenarbeit mit MySQL hat sich das Modul MySQLdb bewährt. Es kann auf der Webseite http://sourceforge.net/projects/mysql-python heruntergeladen werden. Die aktuelle Version ist 1.2.2 und steht als Windows-Installer oder Tar-Archiv für Linux zur Verfügung. Der Windows-Installer kopiert die erforderlichen Dateien in das richtige Verzeichnis. Unter Linux erledigt dies das Skript setup.py, das sich im entpackten Tar-Archiv befindet. Nach der Installation kann in der IDLE getestet werden, ob die Installation erfolgreich war: >>> import MySQLdb
Gelingt diese Anweisung ohne Fehlermeldung, kann das Modul verwendet werden. Hinweis Für viele andere Datenbanksysteme stehen ebenfalls Adapter zur Verfügung. Eine Übersicht bietet die Webseite http://www.python.org/ doc/topics/database/modules/.
Für die Beispiele in diesem Kapitel ist es notwendig, einen MySQL-Datenbank-Server zu installieren oder Zugriff auf einen zu haben. Es werden SQL-Befehle verwendet, um Daten aus der Datenbank zu lesen oder in sie zu schreiben. Eine Einführung in MySQL und seine Handhabung liegt außerhalb des Rahmens dieses Buchs. Ein MySQL Tutorial gibt es auf der Webseite http://dev.mysql.com/doc/refman/5.1/de/tutorial.html. Die Beispiele sollen die kleine Webanwendung aus dem vorigen Abschnitt um eine Datenbankverbindung erweitern. Die Datenbank soll die Inhalte der Seiten zur Verfügung stellen. Dazu wird in MySQL eine Datenbank mit dem Namen python-site erstellt. Diese erhält eine Tabelle mit dem Namen pages. Diese hat folgende Spalten: | id | name | title | text |
Die Spalte id wird für eine fortlaufende Zahl verwendet, die von MySQL automatisch eingetragen wird. Die Spalte name enthält den Namen der Seite. Dies ist der Name, der im Menü auftaucht und auch in der Adresse. Hier muss einmal seite1 und einmal seite2 eingetragen werden. Die Spalte title soll den Titel der Seite enthalten, wie er in der Titelzeile des Browser-Fensters erscheint, und die Spalte text schließlich soll den Seiteninhalt aufnehmen. In diese beiden Spalten kann für jede Seite jeweils beliebiger Inhalt eingefügt werden.
266
9.3 Anbindung an relationale Datenbanken
Der SQL-Befehl zum Aufbau dieser Tabelle lautet: CREATE TABLE 'pages' ('id' TINYINT (3) AUTO_INCREMENT, 'nam'` VARCHAR (50), ......................'title' VARCHAR (50), ......................'text' MEDIUMTEXT, PRIMARY KEY('id') )
9.3.1
Eine Datenbankverbindung aufbauen und Daten auslesen
Bevor Daten aus der Datenbank ausgelesen werden können, muss eine Verbindung zu ihr aufgebaut werden. Dies geschieht mit der Methode connect() des Moduls MySQLdb. >>> import MySQLdb >>> db = MySQLdb.connect(host='localhost', db='Python-site')
Die Methode liefert ein Objekt zurück, das die Verbindung zur Datenbank darstellt. Diese Verbindung ist permanent offen und muss mit der Methode close() explizit wieder geschlossen werden, wenn sie nicht mehr gebraucht wird. Schließt man die Verbindung nicht, kann es z. B. passieren, dass man sehr schnell die Anzahl der maximal offenen Verbindungen zur Datenbank erreicht, mit der Folge, dass keine weiteren Verbindungen zugelassen werden. Neue Nutzer einer Webseite erhalten dann keine Inhalte mehr, die in der Datenbank abgelegt werden. Der Methode connect() muss gesagt werden, mit welchem Host sie die Verbindung herstellen soll und mit welcher Datenbank. Diese beiden Angaben müssen wenigstens gemacht werden, damit eine sinnvolle Verbindung entsteht. Je nach Konfiguration des MySQL-Servers sind aber gegebenenfalls noch andere Angaben notwendig. Befindet sich die Datenbank auf einem entfernten Rechner, wird man sich z. B. beim Verbindungsaufbau authentifizieren müssen. Die folgende Tabelle listet die Parameter auf, die der Methode connect() übergeben werden können: Parameter
Erläuterung
host
Der Host, zu dem die Verbindung hergestellt wird. Standardwert ist localhost.
user
Der Benutzername für die Authentifizierung am Datenbankserver
passwd
Das Passwort für die Authentifizierung am Datenbankserver
db
Der Name der Datenbank, auf die zugegriffen werden soll
port
Die Nummer des Ports, an dem der Datenbankserver läuft. Standardwert ist 3306.
Tabelle 9.2: Mögliche Parameter der Verbindungsfunktion
267
9 Webentwicklung mit Python
Parameter
Erläuterung
connect_timeout
Die Zeitspanne in Sekunden, innerhalb der eine Verbindung zustande gekommen sein muss. Wird der angegebene Wert überschritten, wird die Ausführung von connect() abgebrochen.
compress
Schaltet die Datenkompression ein. Standardwert ist 0.
init_command
Ein Kommando für den Server, das direkt nach dem Verbindungsaufbau ausgeführt wird
Tabelle 9.2: Mögliche Parameter der Verbindungsfunktion (Forts.) Nachdem eine Verbindung zustande gekommen ist, wird mit der Methode cursor() ein sogenanntes Cursor-Objekt erstellt: >>> op = db.cursor()
Das Cursor-Objekt ist quasi die »Arbeitsbiene«, mit deren Methoden man den Zugriff auf die Daten erhält. Folgende Methoden sind für die tägliche Arbeit wichtig: Methode
Erläuterung
excecute(sql-query)
Führt einen SQL-Befehl aus.
executemany(sql-query)
Führt mehrzeilige SQL-Befehle aus.
close(self)
Schließt den Cursor.
fetchall(self)
Gibt alle Ergebnisse einer Anfrage als Tupel zurück. Jede Zeile des Ergebnisses ist wiederum ein Tupel.
fetchmany(anzahl)
Gibt so viele Zeilen als Tupel zurück, wie für anzahl angegeben wurde. Der Cursor steht anschließend hinter der letzten Zeile der Ausgabe.
fetchone()
Gibt die erste Zeile des Ergebnisses zurück und rückt den Cursor um eine Position vor.
scroll(pos, modus)
Setzt den Cursor auf die für pos angegebene Position. Mit dem Parameter modus kann angegeben werden, ob die Position absolut ('absolute') oder relativ ('relative') zur aktuellen Cursorposition gesetzt werden soll. Standardwert ist 'relative'.
nextset()
Führt den Cursor zum nächsten Ergebnis-Set. Ist keines vorhanden, gibt die Methode None zurück.
description
Dieses Attribut enthält nach einer Abfrage Informationen über die Ergebnistabelle. Jede Spalte ist durch ein Tupel repräsentiert, an dessen erster Position der Spaltenname steht. Wurde keine Abfrage getätigt, ist der Wert None.
Tabelle 9.3: Methoden des Cursor-Objekts
268
9.3 Anbindung an relationale Datenbanken
Mit dem Cursor-Objekt kann man nun also folgendermaßen Daten aus der Datenbank auslesen: >>> query = "SELECT * FORM pages" >>> c.execute(query) 2L >>> c.fetchall() ((1, 'seite1', 'seite1', 'Der Inhalt der Seite 2'), (2, 'seite2', 'seite2', 'Der Inhalt der Seite 1.'))
Es wird ein SQL-Befehl als Zeichenkette notiert und an die Methode excecute() übergeben. Sie liefert die Anzahl der Ergebnisse als Long-Integer zurück. Mit fetchall() erhält man dann die Ergebnisse als Tupel.
9.3.2
Die Webanwendung mit der Datenbank verbinden
Bei der Webanwendung geht es darum, den jeweils spezifischen Seiteninhalt aus der Datenbank zu lesen und ihn in die entsprechenden Templates einzufügen. Dazu wird eine Funktion implementiert, die die Verbindung zur Datenbank herstellt, die entsprechende Abfrage an die Datenbank sendet und das Ergebnis in eine Form umwandelt, mit der die bereits existierenden Funktionen gut arbeiten können. Die Funktion wird in das Skript container.py geschrieben: Listing 9.14: Auslesen der Daten aus der Datenbank und umwandeln in ein Dictionary 19 def fetchPageData(seite): 20 # Datenbankverbindung herstellen 21 db = MySQLdb.connect(db='python-site') 22 # Cursor-Objekt erstellen 23 c = db.cursor() 24 # SQL-Anwesisung ausführen 25 c.execute("SELECT * FROM pages WHERE name = '%s'" %(seite)) 26 # Ergebnis auslesen 27 zeile = c.fetchone() 28 ergebnis = {} 29 zaehler = 0 30 # Ergebnis-Dictionary aufbauen 31 for item in c.description: 32 ergebnis[item[0]] = zeile[zaehler] 33 zaehler +=1 34 return ergebnis
Diese Funktion wird später von den Funktionen aufgerufen, die die Seiten bilden. Sie erhält von dort für ihren Parameter seite den Namen der Seite, um die es jeweils geht (Zeile 19). Es wird zuerst die Verbindung zur Datenbank hergestellt (Zeile 21) und anschließend das Cursor-Objekt gebildet (Zeile 23). Dann wird die Abfrage nach den Seiteninhalten abgeschickt (Zeile 25). Der Query-String wird mit dem Wert für den
269
9 Webentwicklung mit Python
Parameter seite gebildet. Damit ist gewährleistet, dass immer der entsprechende Inhalt ausgelesen wird. Aus dem Ergebnis wird dann mithilfe des Attributs description ein Dictionary aufgebaut (Zeile 31–34), in dem die Spaltennamen der Datenbanktabelle als Schlüssel fungieren, denen die entsprechenden Werte zugeordnet sind. Dieses Dictionary gibt die Funktion dann zurück (Zeile 29). Die beiden Funktionen rufen nun diese Funktion auf und erhalten so den Inhalt für »ihre« Seite, hier beispielhaft die Funktion seite1(): Listing 9.15: Modifizierte Funktion für den Seitenaufruf 01 def seite1(req): 02 page_data = fetchPageData('seite1') 03 my_content = content(req,page_data) 04 my_menu = menu(req,MENUDATA) 05 vars = {'content':my_content,'menu':my_menu} 06 return page(req,vars)
Beim Aufruf wird der Name der Seite übergeben (Zeile 01). Der Rückgabewert wird in der Variablen page_data gespeichert. Diese Variable kann nun der Funktion content() anstelle des hart kodierten Dictionary übergeben werden. Es muss allerdings noch eine Änderung am Template content.tmpl vorgenommen werden. Die Variable, die darin den Seiteninhalt entgegennimmt, wurde mit page_content benannt. Jetzt aber ist der Inhalt unter dem Namen text abrufbar. Also muss im Template die entsprechende Änderung vorgenommen werden. Da noch einige Daten mehr in der Datenbank enthalten sind, z. B. ein Titel für jede Seite, kann man in die Templates weitere Variablen integrieren, die diese Daten aufnehmen. Und natürlich lässt sich auch die Datenbank beliebig erweitern, um noch mehr Informationen aufzunehmen. Wer solche Applikationen aufbauen will, braucht, wenn er keinen eigenen Server betreiben will, einen Provider, der mod_python installiert hat. Im Anhang findet sich eine entsprechende Liste mit Providern und ihren Internetadressen. Es gibt auch einige interessante Frameworks, die auf Python basieren und eine Vielzahl von Features bieten. Einige verwenden mod_python, andere nicht. In der PythonLinkliste sind einige dieser Frameworks vermerkt. Wer also nicht jede Funktion selbst entwickeln will, sollte sich deren Webseiten einmal anschauen.
270
10 Bildbearbeitung mit der Python Imaging Library Die Python Imaging Library (PIL) ist das Standardpaket für Python, wenn es darum geht, Bilddateien zu erzeugen und Manipulationen an Bildern vorzunehmen. PIL wurde von dem Schweden Fredrik Lundh entwickelt und liegt aktuell in der Version 1.1.6 vor. Man erhält das Paket unter http://www.pythonware.com/products/pil/index.htm oder http://effbot.org/downloads/. Auf der letztgenannten Seite findet man noch eine ganze Reihe anderer Python-Software von Frederik Lundh. In diesem Kapitel wird ein Einblick in die Library gewährt und ihre grundsätzliche Funktionsweise erläutert. Für umfassende Informationen sei auf die Dokumentation verwiesen, die unter http://www.pythonware.com/library/pil/handbook/index.htm zu finden ist. Mit dem Wissen aus diesem Kapitel und seinen Beispielen lassen sich auch die hier nicht besprochenen Aspekte gut verstehen. Die Installation von PIL ist sehr einfach. Für alle Plattformen wird das Paket Imaging1.1.6.tar.gz benötigt, das nach dem Herunterladen entpackt werden muss. Man erhält ein Verzeichnis mit dem Namen Imaging-1.1.6, indem sich ein Skript mit dem Namen setup.py befindet. Wenn man sich im Ordner Imaging-1.1.6 befindet, ruft man das Skript folgendermaßen auf: # python2.5 setup.py install
Es werden nun eine Reihe von Verzeichnissen und Dateien kopiert. Sie landen allesamt im Verzeichnis site-packages der Python-Installation. Nach der Installation kann in der Python-Shell mit der Zeile import Image
getestet werden, ob das Paket zur Verfügung steht. Wird kein Fehler ausgegeben, ist PIL korrekt installiert und kann nun verwendet werden. Für Windows steht zusätzlich ein Installationsprogramm zur Verfügung, das einem die Arbeit abnimmt. Hier muss beim Herunterladen darauf geachtet werden, dass man den Installer für die richtige Python-Version erhält. Für Python 2.5 ist das die Datei PIL-1.1.6.win32-py2.5.exe.
10 Bildbearbeitung mit der Python Imaging Library
10.1
Bilddateien erzeugen, öffnen, speichern und anzeigen
PIL kann sowohl existierende Bilddateien öffnen und manipulieren als auch neue Bilder erzeugen. Zum Öffnen einer vorhandenen Datei wird die Funktion open() verwendet: import Image bild_obj = Image.open('mein_bild.jpg')
Diese Funktion erwartet als Argument entweder eine Pfadangabe oder ein Dateiobjekt, das im Binärmodus geöffnet wurde. Sie liefert ein Bildobjekt zurück, das für alle weiteren Aktionen verwendet wird. Allerdings enthält dieses Bildobjekt nicht die eigentlichen Bilddaten. Diese werden bei Ausführung von Methoden auf das Bildobjekt geladen. Besteht der Bedarf, vor Ausführung einer Methode die Bilddaten zu laden, kann man dies mit der Methode load() tun: bild_obj.load()
PIL kennt eine ganze Reihe von Dateiformaten. Weiterverarbeiten kann PIL allerdings nur die Formate, die es auch einlesen (laden) kann. Aber auch das kann es mit vielen Formaten, die gebräuchlichsten davon sind: BMP, GIF (89a,87a), JPEG, PNG und TIFF. Bei einigen dieser Formate (BMP, GIF, JPEG, PNG) werden Informationen zum Bild beim Öffnen in das info-Attribut geschrieben und können darüber abgefragt werden (siehe Abschnitt 10.2). Ein neues Bild wird mit der Methode new() erzeugt: import Image bild_obj = Image.new('RGB',(120,90))
Diese Methode benötigt mindestens zwei Argumente, von denen das erste den Bildmodus und das zweite die Größe bestimmt. Die Größe wird als Tupel mit zwei Werten übergeben. Der erste Wert definiert die Breite, der zweite die Höhe des neuen Bilds. Der Wert für den Modus wird als Zeichenkette übergeben. Die folgende Tabelle zeigt, welche Modi zur Verfügung stehen: Modus
Beschreibung
1
Schwarzweißbild mit 1-bit Pixel
L
Graustufenbild mit 8-bit Pixel
P
Farbbild 8-bit Pixel. Farbe wird durch Verwendung einer Farbpalette erzielt.
RGB
Farbbild mit drei Farbkanälen (Rot, Grün, Blau) und 8-bit Pixel
Tabelle 10.1: Von PIL unterstützte Modi
272
10.1 Bilddateien erzeugen, öffnen, speichern und anzeigen
Modus
Beschreibung
RGBA
Farbbild mit drei Farb- (Rot, Grün, Blau) und einem Alphakanal und 8-bit Pixel
CMYK
Farbbild mit drei Farb- (Cyan, Magenta, Gelb) und einem Schwarzkanal und 8-bit Pixel
YCbCr
Farbbild mit drei Kanälen und 8-bit Pixel (PAL-Video)
I
Farbwerte werden als 32-bit signed integer (eine Ganzzahl zwischen 2147483648 und + 2147483647) ausgedrückt.
F
Farbwerte werden als 32-bit floating point (Fließkommazahl mit 4294967296 Stellen) ausgedrückt.
LA
Graustufenbild mit 8-bit Pixeln und einem Alphakanal (nur teilweise unterstützt)
RGBX
RGB mit aufgefülltem Alpha (nur teilweise unterstützt)
RGBa
RGB mit premultipied Alpha (nur teilweise unterstützt). Premultiplied Alpha ist eine Form der Berechnung des Alphakanals eines Bilds.
Tabelle 10.1: Von PIL unterstützte Modi (Forts.) Die Methode new() liefert ebenfalls ein Bildobjekt zurück, das für die weitere Bearbeitung verwendet wird. Über einen dritten Parameter kann die Hintergrundfarbe des Bilds bestimmt werden. Es gibt vier Möglichkeiten, eine Farbe zu bestimmen: Hexadezimal: Der Farbwert wird in hexadezimaler Schreibweise als Zeichenkette notiert, z. B. #ff0088. Dieser Schreibweise liegt das RGB-Farbmodell zu Grunde. RGB: Der Farbwert wird mit Zahlwerten für die Komponenten Rot, Grün und
Blau bestimmt. Übergeben wird entweder eine Zeichenkette in der Form rgb(255,0,127) oder ein Tupel mit den drei Werten. HSL: Hier wird der Farbwert aus den Komponenten Farbwinkel, Farbsättigung
und Farbhelligkeit (Hue, Saturation, Lightness) bestimmt. Der erste Wert bestimmt den Farbton in einem Farbkreis. Hier wird ein Wert zwischen 0 und 360 verlangt. Die beiden übrigen Werten werden als Prozentangaben zwischen 0 und 100% gemacht. Die Angabe wird als Zeichenkette in der Form hsl(330,100%,50%) gemacht. HTML-Farbnamen: Es können die Farbnamen verwendet werden, die auch in
HTML-Seiten benutzt werden. Der Name wird als Zeichenkette angegeben.
273
10 Bildbearbeitung mit der Python Imaging Library
Tipp Weiterführende Informationen zu den Farbmodellen findet man im Netz unter http://de.wikipedia.org/wiki/Farbraum
Zum Speichern eines Bilds wird die Methode save() auf ein Bildobjekt angewendet: bild_obj.save('mein_bild.jpg')
Als Parameter erwartet die Methode eine Pfadangabe oder ein Dateiobjekt. Wenn ein Pfad angegeben wird, ermittelt die Methode aus der Dateiendung das Format, in dem die Bilddatei gespeichert werden soll. Wenn man aus bestimmten Gründen keine Standardendung für das Dateiformat verwenden kann, ist es möglich, das Format mit einem zweiten Parameter anzugeben: bild_obj.save('mein_bild.xxx','JPEG')
Neben anderen weniger gebräuchlichen kann PIL folgende wichtige Bildformate schreiben: BMP, GIF (87a), JPEG (progressive), PCX, PDF, PNG und TIFF. Wenn man ein von PIL erzeugtes oder manipuliertes Bild betrachten will, muss man nicht unbedingt den Weg über das Speichern und anschließendes Öffnen in einem Bildbearbeitungsprogramm gehen. Mit der Methode show() ist es möglich, ein Bild direkt anzeigen zu lassen: bild_obj.show()
Unter Windows wird dazu der Standardbildbetrachter verwendet, unter Linux wird das Bild mit xv angezeigt. Diese Methode ist sehr hilfreich bei der Entwicklung, da man mit ihrer Hilfe schnell die Ergebnisse seines Codes testen kann.
10.2
Bildattribute auslesen
Jedes Bild, das von PIL geöffnet oder erzeugt wird, besitzt Eigenschaften, die ausgelesen werden können. Diese sind: format – hierüber kann das Dateiformat der Bilddatei ausgelesen werden. Man er-
hält eine Zeichenkette mit dem Formatkürzel. Wenn es sich um ein mit PIL erzeugtes Bildobjekt handelt, ist dieses Attribut None. mode – gibt den Farbmodus des Bildobjekts wieder. Man erhält eine Zeichenkette
mit dem Moduskürzel (siehe Tabelle 10.1).
274
10.2 Bildattribute auslesen
size – dieses Attribut enthält die Abmessungen des Bilds als Tupel mit den Ganz-
zahlen für Breite und Höhe. palette – über dieses Attribut erhält man die Farbpalette des Bildobjekts, sofern es eine besitzt. Andernfalls hat das Attribut den Wert None. Die Palette ist ihrerseits ein
Objekt, das Attribute und Methoden besitzt. info – liefert ein Dictionary mit Zusatzinformationen zur Bilddatei. Diese Informa-
tionen variieren je nach Bildformat. Wird ein Bild, das von einem anderen Programm erzeugt wurde, mit PIL gespeichert, gehen diese Informationen verloren. Bei einigen Formaten schreibt PIL beim Speichern eigene Informationen in das Dictionary. Einige Beispiele sollen die Ausgaben der Attribute verdeutlichen: >>> from PIL import Image >>> bild_obj = Image.open('mein_bild.jpg') >>> bild_obj.format 'JPEG' >>> bild_obj.mode 'RGB' >>> bild_obj.size (120, 90)
Da JPEG-Bilder keine Paletten besitzen, wird nun, um das palette-Attribut zu zeigen, ein GIF-Bild geladen: >>> gif_bild = Image.open('mein_bild2.gif') >>> pal = gif_bild_palette >>> pal
Wie erwähnt, ist das Ergebnis ein Paletten-Objekt. Man kann mit der dir-Funktion schauen, welche Namen dieses Objekt besitzt: >>> dir(pal) ['__doc__', '__init__', '__module__', 'colors', 'dirty', 'getcolor', 'getdata', 'mode', 'palette', 'rawmode', 'save', 'tostring']
Wie man sieht, besitzt das Paletten-Objekt einige Attribute und Methoden, mit denen man Paletten manipulieren kann. Unter Umständen kann PIL mit Paletten, die von anderen Programmen erzeugt wurden, nicht umgehen, so dass das Attribut colors und die Methode getcolor() keine Ergebnisse liefern. Schließlich bleibt noch die Ausgabe des info-Attributs zu zeigen: >>> gif_bild.info {'duration': 0, 'version': 'GIF89a', 'background': 250}
275
10 Bildbearbeitung mit der Python Imaging Library
Dies sind die Informationen, die ein GIF-Bild liefert. Der Schlüssel duration steht für die Länge einer GIF-Animation, der Wert für background bezeichnet den Index der Hintergrundfarbe in der verwendeten Palette. Das vorige JPEG-Bild zeigt diese Informationen: >>> bild_obj.info {'jfif': 258, 'jfif_unit': 0, 'jfif_version': (1, 2),'jfif_density': (100, 100)}
Die Abkürzung »jfij«, die hier jedem Schlüssel vorangestellt ist, steht für JPEG Interchange File Format. Dieses Format dient dem Austausch von JPEG-komprimierten Bildern auf unterschiedlichen Plattformen. Bei Bildern, die mit einer digitalen Kamera aufgenommen wurden, die in der Regel auch im JPEG-Format vorliegen, können auch die EXIF-Daten enthalten sein.
10.3
Maße ändern, drehen, Kopien und Ausschnitte erzeugen
Man wird in der Regel mit vorhandenem Bildmaterial arbeiten und da hat man es häufig mit Bildern aus Digitalkameras zu tun. Will man diese z. B. auf einer Webseite anzeigen, benötigt man oftmals eine andere als die Ausgangsgröße oder man möchte zunächst nur eine verkleinerte Darstellung (Thumbnail) anzeigen, die dann per Klick vom Benutzer vergrößert werden kann. Ein anderes Problem bei Bildern aus der Digitalkamera sind hochformatige Bilder. Diese müssen für die richtige Anzeige zunächst gedreht werden. PIL bietet alle Werkzeuge, die man benötigt, um diese Aufgaben zu lösen. Zunächst ein Skript, das alle Bilder eines Ordners auf eine bestimmte Abmessung bringt und dafür die Methode resize() benutzt: Listing 10.1: Bildabmessungen verändern 01 import os 02 import Image 03 04 def resize_all(mass1): 05 # Bildordner bestimmen 06 bild_ordner = os.getcwd()+'\\bilder' 07 # Ordnerinhalt auslesen 08 bild_dateien = os.listdir(bild_ordner) 09 # Schleife über alle Dateien im Ordner 10 for bild in bild_dateien: 11 bild_pfad = bild_ordner+'\\'+bild 12 # Bilddatei öffnen 13 bild_obj = Image.open(bild_pfad) 14 # neue Bildgröße berechnen
276
10.3 Maße ändern, drehen, Kopien und Ausschnitte erzeugen
Das Skript importiert zunächst die notwendigen Module. Neben dem Image-Modul wird hier auch os benötigt, um das Verzeichnis auszulesen, in dem sich die zu bearbeitenden Bilder befinden. Die Funktion resize_all() geht davon aus, dass sich die Bilder in einem Verzeichnis mit Namen bilder innerhalb des Verzeichnisses, in dem das Skript selbst liegt, befindet (Zeile 06). Mit der Funktion listdir() des os-Moduls wird der Inhalt des Verzeichnisse ausgelesen. Die Ergebnisliste wird dann mit einer for-Schleife durchlaufen (Zeile 10). Für jedes Bild wird nun der Pfad gebildet und anschließend ein Bildobjekt erzeugt (Zeile 13). Die Größenumwandlung soll von der längeren Seite des Originalbilds ausgehen. Diese soll auf die Länge gebracht werden, die der Funktion übergeben wird (mass1). Die andere, kürzere Bildseite soll proportional umgerechnet werden. Aus diesem Grund wird zunächst geprüft, ob die Bildbreite einen höheren Wert als die Bildhöhe hat (Zeile 15). Dies ist bei einem Querformat der Fall. Entsprechend wird ein zweiter Wert berechnet (Zeile 17) und anschließend ein Tupel mit den neuen Abmessungen gebildet (Zeile 18). Für ein hochformatiges Bild wird die Berechnung entsprechend im else-Block vorgenommen. Mit diesen neuen Werten wird nun die eigentliche Größenänderung vorgenommen (Zeile 24). Die Methode resize() arbeitet mit einer Kopie des ursprünglichen Bildobjekts und liefert dieses zurück. Fast alle Methoden des Image-Moduls arbeiten auf diese Weise. Deshalb ist es notwendig, das Ergebnis der Methode einer Variablen zuzuweisen. Im Beispielskript wird einfach die vorhandene Variable bild_obj überschrieben, da nach der Größenänderung das Originalbild nicht mehr benötigt wird. Die Methode resize() bekommt also als Parameter einen Tupel mit den neuen Abmessungen übergeben. Bei der Größenumwandlung wendet die Methode einen Filter an, um Unschärfen, die bei der Umwandlung entstehen, auszugleichen. Man kann mit einem zweiten Parameter bestimmen, welchen Filter die Methode anwenden soll. Dies geschieht in der folgenden Form: bild_obj = bild_obj.resize(new_size,Image.BICUBIC)
277
10 Bildbearbeitung mit der Python Imaging Library
Es können vier Filter angewendet werden: NEAREST – einfacher Filter, der als Standardfilter verwendet wird. Kann zu »pixeligen« Bildern führen. Wird automatisch bei Schwarzweißbildern (Moduls '1') und Bildern mit Palette verwendet (Modus 'L'). BILINEAR – schnell ausführbarer Filter, der bessere Ergebnisse erzielt als NEAREST,
aber Treppenstrukturen aufweisen kann. BICUBIC – erzielt sehr natürlich aussehende Ergebnisse, die »weicher« aussehen als bei der Verwendung von BILINEAR. ANTIALIAS – glättet vor allem Konturen und verhindert so Treppenstrukturen.
Tipp Eine kurze Beschreibung der Unterschiede dieser und anderer Filter findet man auf der Webseite http://www.matuschek.net/interpolation/.
10.3.1
Vorschaubilder erzeugen
Eine andere Anwendung für die Größenänderung ist die Erzeugung von kleinen Vorschaubildern, sogenannte Thumbnails. PIL hat dafür eine eigene Methode, die im folgenden Skript zum Einsatz kommt: Listing 10.2: Thumbnails erzeugen 01 import os 02 import Image 03 04 def thumb_all(size): 05 # Bildordner bestimmen 06 bild_ordner = os.getcwd()+'\\bilder' 07 # Bilddatein auslesen 08 bild_dateien = os.listdir(bild_ordner) 09 #Schleife über alle Bilder 10 for bild in bild_dateien: 11 bild_pfad = bild_ordner+'\\'+bild 12 # Bild öffnen 13 bild_obj = Image.open(bild_pfad) 14 # Größenänderung 15 bild_obj.thumbnail(size,Image.ANTIALIAS) 16 # neuen Dateinamen generieren 17 new_name = os.path.splitext(bild_pfad)[0] + ".thumbnail" 18 # Bild speichern 19 bild_obj.save(new_name,'JPEG')
278
10.3 Maße ändern, drehen, Kopien und Ausschnitte erzeugen
Im Prinzip läuft es genauso wie im vorigen Skript. Der Unterschied ist hier, dass die Größe des Vorschaubilds nicht errechnet, sondern durch die Parameterübergabe festgelegt wird. Die Methode thumbnail() erwartet als Parameter eine Größenangabe als Tupel und kann über einen zweiten Parameter in Bezug auf den Interpolierungsfilter ebenfalls gesteuert werden. Anders als bei resize() aber erzeugt sie keine Kopie des Bildobjekts, sondern modifiziert das Originalbild, so dass eine Zuweisung an eine Variable nicht notwendig ist (Zeile 15). Über das Speichern unter einem Namen mit eigener Erweiterung (Zeilen 17 und 19) wird gewährleistet, dass die Originaldatei erhalten bleibt. Im Ergebnis erhält so jede Originalbilddatei eine Thumbnail-Datei zur Seite gestellt.
10.3.2
Bilder drehen
Hochformatige Bilder aus Digitalkameras müssen gedreht werden, damit sie richtig angezeigt werden können. Das folgende Skript erledigt dies mit allen Bildern in einem Ordner in einem Rutsch: Listing 10.3: Alle Bilder eines Ordners drehen 01 import os 02 import Image 03 04 def rotate_all(winkel): 05 # Bildordner bestimmen und Dateien auslesen 06 bild_ordner = os.getcwd()+'\\bilder' 07 bild_dateien = os.listdir(bild_ordner) 08 # Für alle Bilder im Ordner 09 for bild in bild_dateien: 10 bild_pfad = bild_ordner+'\\'+bild 11 # Bild öffnen 12 bild_obj = Image.open(bild_pfad) 13 # Bild drehen 14 bild_obj = bild_obj.rotate(winkel) 15 # Bild speichern 16 bild_obj.save(bild_pfad,'JPEG')
Die Methode, die PIL zum Drehen von Bildern zur Verfügung stellt, heißt rotate(). Als Parameter wird der Winkel eingegeben, um den das Bild gedreht werden kann. Die Drehung erfolgt gegen den Uhrzeigersinn, so dass für eine Drehung um 90 Grad im Uhrzeigersinn der Wert 270 eingegeben werden muss. Die Methode rotate() erzeugt genau wie resize() eine Kopie des Originalbilds und modifiziert diese. Auch rotate() wendet nach der Rotation einen Filter an, um dem neu berechneten Bild mehr Schärfe zu geben. Mit einem zweiten Parameter, hier aber als Schlüsselwortargument, kann der Filter bestimmt werden: bild_obj = bild_obj.rotate(270, filter=Image.BICUBIC)
279
10 Bildbearbeitung mit der Python Imaging Library
Ein dritter Parameter – expand – bestimmt, ob das gedrehte Bild die gleichen Abmessungen wie das Originalbild hat, oder ob es sich mit seinen Abmessung der Drehung anpasst. Dies ist bei Drehungen interessant, die nicht um 90 oder 180 Grad erfolgen. Bei einer Drehung z. B. um 15 Grad ist das kleinst mögliche Rechteck, das sich um das gedrehte Bild ziehen lässt, größer als das Ausgangsbild. Setzt man den Parameter expand auf den Wert 1, entsprechen die Bildmaße nach der Rotation diesem Rechteck: bild_obj = bild_obj.rotate(270, filter=Image.BICUBIC, expand = 1)
Abbildung 10.1: Funktionsweise des Parameters expand
10.3.3
Bildteile kopieren und einfügen
Das folgende Beispiel kopiert aus einem vorhandenen Bild einen zufällig gewählten Ausschnitt und setzt diesen in ein neu erstelltes Bild so oft ein, dass die gesamte Bildfläche damit gleichmäßig ausgefüllt wird. Auf diese Weise entsteht ein Muster, das als Textur verwendet werden kann: Listing 10.4: Ein Bild mit einem zufälligen Muster erzeugen 01 from PIL import Image 02 import random 03 04 def create_muster(bild_dim,kachel_dim,vorlage,bildname): 05 # Vorlagenbild öffnen 06 try: 07 vorlage = Image.open(vorlage) 08 except: 09 print 'Bild konnte nicht gefunden werden' 10 return 11 12 # Abmessung in Einzelwerte zerlegen 13 mw, mh = bild_dim 14 kw, kh = kachel_dim 15 16 # Verhältnis von Kachel- und Zielbildgröße prüfen 17 if mw%kw or mh%kh: 18 print 'Das Verhältnis von Kachel und Bildabmessung stimmt nicht'
280
10.3 Maße ändern, drehen, Kopien und Ausschnitte erzeugen
Neben dem obligatorischen Import des Image-Moduls wird dieses Mal auch das Modul random benötigt, um eine Zufallsauswahl zu treffen. Die Funktion verlangt vier Parameter, mit denen die Größe des erzeugten Bilds (bild_dim) und die Abmessung der Kachel (kachel_dim) bestimmt werden. Diese beiden Werte müssen als Tupel in der Form (breite, höhe) übergeben werden. Mit den beiden weiteren Parametern wird das Ausgangsbild (vorlage) und der Name des zu erzeugenden Bilds (bildname) bestimmt. Diese beiden Werte werden als Zeichenketten übergeben. Zunächst wird versucht, das Vorlagenbild zu öffnen. Schlägt dies fehl, wird eine entsprechende Meldung ausgegeben und der Funktionsablauf mit der return-Anweisung gestoppt (Zeilen 06–10). In den Zeilen 13 und 14 werden die Abmessungen des erzeugenden Bilds und der Kachel auf einzelne Variablen übertragen. Dies erfolgt, um zu prüfen, ob das Verhältnis von Kachel- und Bildabmessung so bestimmt wurde, dass die Kachel auch am Bildrand komplett eingefügt wird (Zeile 17–19). Dazu muss die Bildbreite ein Vielfaches der Kachelbreite und die Bildhöhe ein Vielfaches der Kachelhöhe sein. Mit einer Modulooperation kann man diesen Test sehr effektiv durchführen. In Zeile 18 wird schließlich das neue Bild erzeugt. Nun wird aus dem Vorlagenbild ein Rechteck an einer zufällig ermittelten Position kopiert. Dies geschieht mit der Funktion crop(), die diesen Ausschnitt als neues Bildobjekt zurückliefert. Die Funktion erwartet einen Tupel mit vier Werten als Parameter, die vier Werte beschreiben die linke, obere, rechte und untere Seite des Rechtecks,
281
10 Bildbearbeitung mit der Python Imaging Library
das kopiert werden soll. In der Funktion wird jeweils der Wert für die linke und obere Seite zufällig ermittelt, indem ein Wert aus einer Zahlensequenz gewählt wird, die aus der Breite bzw. der Höhe des Vorlagenbilds gebildet wird (Zeilen 24–31). Jetzt muss diese Kachel in das neu erstellte Bild eingefügt werden. Dazu steht die Funktion paste() zur Verfügung. Mit ihrem ersten Parameter wird das Bildobjekt bestimmt, das eingefügt wird, mit dem zweiten wird die Position bestimmt, an der es eingefügt wird. Man kann ein Tupel mit zwei oder vier Werten übergeben. Bei zwei Werten bestimmt man, wo das Bild mit seiner linken oberen Ecke platziert wird, bei vier Werten wird die Position der linken, oberen, rechten und unteren Seite festgelegt. Im letzteren Fall muss die Abmessung des einzufügenden Bilds mit der Dimension übereinstimmen, die vom Tupel definiert wird. Wenn die Bildmodi der beiden Bilder, die beim Einfügen involviert sind, nicht übereinstimmen, wird das einzufügende Bild zum Modus des aufnehmenden Bilds konvertiert. Fügt man also ein RGB-Bild in ein Graustufenbild (Modus L) ein, so wird das RGB-Bild in Graustufen umgewandelt. In der Funktion wird mit einer verschachtelten Schleife die Kachel von links oben beginnend horizontal fortlaufend eingefügt (Zeile 34–36). Am Ende muss das neue Bild nur noch gesichert und eine Erfolgsmeldung ausgegeben werden. Ein Aufruf der Funktion kann z. B. so aussehen: >>> create_muster(bild_dim=(400,300), kachel_dim=(20,15), vorlage="vorlage.jpg", muster='muster.jpg') Bild erstellt
Das Vorlagenbild muss dabei im gleichen Verzeichnis sein, wie das Skript. Das erzeugte Musterbild wird ebenfalls dort abgelegt. Es kann etwa aussehen wie in Abbildung 10.2 gezeigt.
Abbildung 10.2: Erzeugtes Musterbild
282
10.4 Kanäle und Masken
Die Funktion paste() kann auch eine einfache Farbfläche in ein Bild einfügen. Dazu wird als erster Parameter ein Farbwert wie in Abschnitt 10.1 beschrieben angegeben. Für die Positionierung des Rechts muss hier ein Tupel mit vier Werten verwendet werden, weil sich nur so die Region exakt beschreiben lässt. bild_obj.paste('rgb(255,0,0)', (100,100,180,150))
Will man nicht nur einen Ausschnitt aus einem Bild kopieren, sondern das komplette Bild, könnte man das zwar auch mit der Methode crop() machen, indem man die Region entsprechend definiert, aber dies geht auch einfacher mit der Methode copy(): bild_kopie = bild_obj.copy()
Wie gewohnt erhält man ein Bildobjekt zurück, mit dem weitergearbeitet werden kann. Schließlich sei noch darauf hingewiesen, dass die Umwandlung des Bildmodus, die ggf. beim Einfügen eines Bilds automatisch erfolgt, auch selbst vorgenommen werden kann. Dafür bietet PIL die Methode convert(), die als Parameter die Angabe des neuen Modus (siehe Tabelle 10.1 in Abschnitt 10.1) erwartet: con_bild = bild_obj.convert('L')
Und auch diese Methode gibt ein neues Bildobjekt zurück.
10.4
Kanäle und Masken
Ein Bild im RGB-Modus besteht wie schon erwähnt aus drei Kanälen, je einem für die Rot-, Grün- und Blauanteile des Bilds. Es kann auch noch ein vierter Kanal hinzukommen, der sogenannte Alphakanal, der Transparenzen im Bild definiert. Manchmal ist es notwendig, Kanäle einzeln zu bearbeiten, etwa wenn man ein rotstichiges Bild farblich korrigieren möchte. Mit der Methode split() kann man ein Bild in seine einzelnen Kanäle zerlegen. Das Gegenstück dazu ist die Methode merge(), die ein Bild aus einzelnen Kanälen zusammensetzt. Diese beiden Methoden verschaffen einem die Möglichkeit, sich einen einzelnen Kanal zur Bearbeitung vorzunehmen und das Ergebnis wieder ins Bild einzufügen. Das folgende Skript macht genau das: Listing 10.5: Bearbeitung eines einzelnen Kanals 01 import Image, ImageEnhance 02 def kanal_manipulation(bild,kanal,wert,ergebnis): 03 # Bild öffnen 04 bild = Image.open(bild) 05 # Bildkanäle ermitteln 06 kanal_ids = list(bild.getbands()) 07 # Indizes der Kanäle bestimmen 08 i = kanal_ids.index(kanal)
283
10 Bildbearbeitung mit der Python Imaging Library
09 10 11 12 13 14 15 16 17 18 19
# Bild in Liste aus Kanalbildern aufteilen kanalbilder = list(bild.split())
# einzelnes Kanalbild bestimmen akt_kanal = kanalbilder[i] # Helligkeit des Kanalbilds ändern akt_kanal = ImageEnhance.Brightness(akt_kanal).enhance(wert) # verändertes Kanalbild in Liste einfügen kanalbilder[i] = akt_kanal # Bild aus den Kanalbildern neu zusammenfügen bild = Image.merge('RGB',kanalbilder)
bild.save(ergebnis)
Die Funktion kanal_manipulation() hat vier Parameter, die für das zu bearbeitende Bild (bild), den zu bearbeitenden Kanal (kanal), einen Wert für die Kanalbearbeitung (wert) und das Ergebnisbild (ergebnis) stehen. Nachdem das Bild geöffnet wurde, werden die Bezeichnungen seiner Kanäle mit der Methode getbands() ermittelt (Zeile 06). Sie gibt ein Tupel mit den Kürzeln für die einzelnen Kanäle zurück, z. B. ('R','G','B'). Das Tupel wird direkt in eine Liste umgewandelt, damit es möglich ist, den Index des Kanals abzufragen, der bearbeitet werden soll (Zeile 08). Anschließend wird das Bild in seine Kanäle zerlegt (Zeile 10). Die Methode split() liefert ein Tupel mit Bildobjekten für jeden Kanal. Mit dem zuvor ermittelten Index kann dann das Kanalbild herausgefiltert werden, das bearbeitet werden soll (Zeile 12). Es wird lediglich an der Helligkeit des Kanalbilds manipuliert (Zeile 14). Das manipulierte Bild wird dann wieder in die Liste der Kanalbilder eingefügt und zwar am gleichen Platz, an dem es sich auch zuvor befand (Zeile 16). Jetzt muss aus diesen Kanälen das Bild wieder neu erstellt werden. Dies erledigt die Methode merge() (Zeile 18). Sie erhält als ersten Parameter die Kennung für den Modus des zu erstellenden Bilds und als zweiten die Sequenz mit den Bildobjekten der Kanäle. Anhand des Modus wird das Bild erstellt, d. h. beispielsweise bei RGB, das erste Bildobjekt in der Sequenz repräsentiert den Rot-, das zweite den Grün- und das dritte den Blauanteil des Gesamtbilds. Schließlich muss das bearbeitete Bild noch gespeichert werden. Ein Aufruf der Funktion sieht z. B. folgendermaßen aus: kanal_manipulation('vorlage.jpg','R',1.5,'ergebnis.jpg')
Hier würde der Rotkanal so bearbeitet, dass das Bild deutlich mehr Rotanteile bekommt.
10.4.1
Masken verwenden
Eine weitere Möglichkeit, Bilder zu manipulieren, sind Masken. Eine Maske ist dabei nichts anderes als ein Bildobjekt, das im Modus 1, L oder RGBA vorliegt. Beim RGBAModus wird nur der Alphakanal als Maske verwendet.
284
10.5 Zeichnen und Zeichen
Eine Maske kann mit der Methode paste() (siehe voriger Abschnitt) oder der Methode composite() verwendet werden. In beiden Fällen funktioniert das Prinzip auf die gleiche Weise: Die Maske bestimmt, wo und in welcher Intensität etwas eingefügt wird. Bei der Methode paste() wird die Maske als dritter Parameter angeben: bild_obj.paste(teilbild, region, maske)
Es ist darauf zu achten, dass maske und teilbild die gleichen Abmessungen haben. Die Methode composite erzeugt ein neues Bildobjekt aus zwei Bildern, die die gleichen Abmessungen haben müssen. Die beiden Bilder werden dabei überlagert. Die Maske, die ebenfalls die gleichen Abmessungen haben muss, bestimmt, welche Pixel deckend, transparent oder gar nicht aufgetragen werden: neu_bild = Image.compose(bild1, bild2, maske)
Bei der Verarbeitung wird bild2 quasi auf bild1 gelegt. Hätte man eine rein schwarze Maske, würde bild2 bild1 komplett verdecken. Hellere Pixel in der Maske hingegen, lassen bild1 durchscheinen.
10.5
Zeichnen und Zeichen
Mit dem Modul ImageDraw steht ein Tool zur Verfügung, mit dem Zeichnungen und Text in Bildern erstellt werden können. Das Prinzip ist, dass man ein Zeichnenobjekt erstellt, dem ein Bildobjekt übergeben wird. Die Methoden des Zeichnenobjekts modifizieren dann das Bildobjekt direkt: import Image, ImageDraw bild_obj = Image.new('RGB',(400,300),'white') zeichnen = ImageDraw.Draw(bild_obj)
Neben dem Import des Image-Moduls muss auch das Modul ImageDraw importiert werden. Mit dem Aufruf der Klasse Draw wird das Zeichnenobjekt erstellt. Das folgende Skript zeichnet ein einfaches Kreuz (zwei Linien) in ein Bild: Listing 10.6: Zwei Linien in ein Bild zeichnen 01 import Image, ImageDraw 02 03 def zeichnen_test(): 04 bild = Image.new('RGB',(400,300),'white') 05 zeichnen = ImageDraw.Draw(bild) 06 b,h = bild.size 07 zeichnen.line(((b/2, 0), (b/2, h)), width=5, fill='red') 08 zeichnen.line((0, h/2, b, h/2), width=5, fill='red') 09 del zeichnen 10 bild.save('zeichnen.jpg')
285
10 Bildbearbeitung mit der Python Imaging Library
Zum Zeichnen von Linien ist die Methode line() vorgesehen. Man übergibt ihr mit einem Tupel die Koordinaten von Anfangs- und Endpunkt der Linie. Zeile 07 zeigt eine Möglichkeit, die Koordinaten anzuzeigen, bei der die beiden Punkte jeweils in ein Tupel notiert werden. Der erste Wert steht für die horizontale, der zweite für die vertikale Ausrichtung des Punkts. Bei der zweiten Methode der Punktedefinition in Zeile 08 werden die vier Werte in nur einem Tupel notiert. Die ersten beiden beschreiben den Anfangs-, die letzten beiden den Endpunkt der Linie. Ausgangspunkt für die Bestimmung von Punkten im Bild ist die linke obere Ecke. Sie hat die Koordinate (0,0). Entsprechend hat die rechte untere Ecke die Koordinate bild.size.
Mit dem Parameter width wird die Linienstärke bestimmt, während mit fill die Linienfarbe angegeben wird. Lässt man beide Parameter weg, wird eine 1-Pixel breite weiße Linie gezeichnet. Wenn das Zeichnenobjekt nicht mehr benötigt wird, kann man es mit der del-Anweisung aus dem Arbeitsspeicher löschen (Zeile 09) Ruft man die Funktion im Skript auf, erhält man folgendes Bild:
Abbildung 10.3: Das Ergebnis: zwei Linien im Bild Das Modul ImageDraw stellt eine Reihe weiterer Methoden zum Zeichnen zur Verfügung, die alle in der gleichen Weise angewendet werden. Die folgende Tabelle listet sie auf und gibt eine kurze Erläuterung: Methode
Beschreibung
arc(rechteck,start,ende, optionen)
Zeichnet einen Bogen in das angegebene Rechteck, zwischen dem Start- und Endwinkel. Mit weiteren Optionen können Farbe und Linienstärke bestimmt werden.
bitmap(position,bitmap, optionen)
Setzt ein Graustufen- oder Schwarzweißbild an der angegebenen Position ein.
Tabelle 10.2: Methoden des Draw-Objekts
286
10.5 Zeichnen und Zeichen
Methode
Beschreibung
chord(rechteck,start,ende, optionen)
Zeichnet einen Bogen in das angegebene Rechteck, zwischen dem Start- und Endwinkel. Mit weiteren Optionen können Farb- und Linienstärke bestimmt werden. Im Unterschied zu arc() werden die beiden Endpunkte mit einer geraden Linie verbunden. Mit der Option fill kann eine Füllfarbe für die gezeichnete Form angegeben werden
ellipse(rechteck,optionen)
Zeichnet eine Ellipse in das angegebene Rechteck. Ist das Rechteck ein Quadrat, erhält man einen Kreis. Mit der Option fill kann eine Füllfarbe für die gezeichnete Form angegeben werden
pieslice(rechteck,start,ende, optionen)
Zeichnet ein »Tortenstück«. Die Funktionsweise ist die gleiche wie bei arc() und chord(). Es werden aber gerade Linien zum Mittelpunkt des Rechtecks gezogen.
point(koordinate(n), optionen)
Zeichnet einen Punkt an der angegebenen Koordinate. Es ist möglich, durch Angabe mehrerer Koordinaten als Sequenz mehrere Punkte gleichzeitig zu zeichnen.
polygon(kordinaten, optionen)
Zeichnet ein Polygon zwischen den angegebenen Koordinaten. Mit der Option fill kann eine Füllfarbe für die gezeichnete Form angegeben werden.
rectangel(rechteck,optionen)
Zeichnet ein Rechteck an der angegebenen Position. Mit der Option fill kann eine Füllfarbe für die gezeichnete Form angegeben werden.
Tabelle 10.2: Methoden des Draw-Objekts (Forts.)
Tipp Diese Zeichnenmethoden sind für einfachste Zeichnungen gedacht. Weitaus komplexere Methoden, mit denen anspruchsvollere Zeichnungen erstellt werden können, bietet das Modul aggdraw, das zusammen mit PIL verwendet werden kann. Es kann unter der Adresse http://effbot.org/downloads/#aggdraw heruntergeladen werden. Eine englischsprachige Dokumentation ist unter http://effbot.org/zone/ pythondoc-aggdraw.htm zu finden.
10.5.1
Text ins Bild einfügen
Genauso wie geometrische Formen wird auch Text über eine Methode des DrawModuls in das Bild geschrieben. Allerdings muss dafür zunächst ein Zeichensatz geladen werden. Dies geschieht über das Modul ImageFont. In früheren Versionen konnte PIL nur mit einem eigenen Format von Bitmap-Fonts umgehen. Mittlerweile
287
10 Bildbearbeitung mit der Python Imaging Library
aber kann es TrueType- und Opentype-Fonts laden, so dass man mit der gesamten Auswahl an Schriften, die man auf dem Rechner installiert hat, arbeiten kann. Wer dennoch mit den PIL-Fonts arbeiten will, kann diese unter http://effbot.org/media/downloads/pilfonts.zip herunterladen. Das folgende Skript zeigt exemplarisch den gesamten Vorgang, der nötig ist, um die Worte Hallo Bild in eine Bilddatei zu schreiben: Listing 10.7: Schreiben im Bild 01 import Image, ImageDraw, ImageFont 02 03 def schreibe(): 04 bild = Image.new('RGB',(400,300),'white') 05 draw = ImageDraw.Draw(bild) 06 07 arial = ImageFont.truetype("arial.ttf", 18) 08 draw.text((20, 20), "Hallo Bild", font=arial,fill=0) 09 bild.save('textbild.jpg')
Zusätzlich muss für das Laden des Zeichensatzes das Modul ImageFont importiert werden (Zeile 01). Mit der Methode truetype() wird ein Font-Objekt gebildet. Ihr wird der Dateiname eines installierten Zeichensatzes und eine Schriftgröße übergeben (Zeile 07). Mit diesem Font-Objekt kann nun mit der Methode text() des Zeichnenobjekts Text in das Bild »gezeichnet« werden (Zeile 08). Auch diese Methode verlangt eine Koordinate, an der der Text eingesetzt wird.. Als zweiter Parameter wird der Text selbst übergeben. Das Argument font nimmt dann das zuvor gebildete FontObjekt entgegen und schließlich kann auch hier mit fill eine Farbe für den Text festgelegt werden. Für die exakte Positionierung von Text kann es wichtig sein, die Abmessungen zu kennen, die ein Text, der mit einem Font-Objekt geschrieben wird, hat. Wenn man z. B. einen Text links von einem Objekt platzieren möchte, muss man die Breite wissen, die dieser Text einnimmt. Die Abmessung lässt sich mit der Methode getssize() des Font-Objekts ermitteln. Man übergibt der Methode den Text, der geschrieben werden soll, und diese liefert ein Tupel mit Breite und Höhe des Textblocks zurück: >>> arial = ImageFont.truetype("arial.ttf", 18) >>> arial.getsize('Hallo Bild') (75, 21)
10.6
Ein Captcha erstellen
Ein Captcha ist ein kleines Bild mit einer zufälligen Buchstabenkombination, das im Idealfall von Programmen nicht gelesen werden kann, wohl aber vom menschlichen Auge. Solche Bilder werden genutzt, um Foren oder auch Kontaktformulare vor Spam zu schützen. Nur wenn die Buchstabenkombination in ein Eingabefeld eingetippt wird, wird die Funktion des Programms (Abschicken einer E-Mail, Abspeichern
288
10.6 Ein Captcha erstellen
eines Forenbeitrags ...) ausgelöst. Captcha ist ein Akronym für Completely automated public Turing test to tell computers and humans apart. Damit ist ein vollautomatischer Turing-Test zum Unterscheiden von Computern und Menschen gemeint. Der TuringTest geht auf Alan Turing zurück, der damit die Frage klären wollte, ob Maschinen denken können. Natürlich ist es auch mit Software möglich, Bildinhalte auszulesen. Man denke nur an OCR-Software, die in der Lage ist, eingescannte Textseiten zu »lesen« und in Textverarbeitungsformate umzuwandeln. Aus diesem Grund reicht es nicht aus, nur ein Bild mit klaren Buchstaben zu erstellen. Vielmehr ist es Aufgabe eines Captcha, diesen Programmen »das Leben schwer« zu machen, indem bewusst Undeutlichkeiten und Unregelmäßigkeiten in das Bild eingefügt werden. Dem menschlichen Auge ist es möglich, verdrehte oder verzerrte Buchstaben vor unruhigem Hintergrund zu erkennen. Im Folgenden werden die bisherigen Beispiele mit der Python Imaging Library zusammengefasst und erweitert zu einem Modul, das dynamisch ein Captcha erzeugt. Das Modul besteht aus der Klasse Captcha, deren Attribute und Methoden das folgende Klassendiagramm zeigen. Captcha __c_size __captcha __color __captcha_pic __code_num __init__() random_color() create_captcha create_bg() draw_lines() write_code()
Es sind im Wesentlichen die drei Methoden create_bg(), draw_lines() und write_code(), die das Captcha-Bild erstellen. Die Methode create_captcha() ruft diese drei Methoden auf und speichert das Ergebnis. Die Methode random_color() erzeugt eine zufällige Farbe aus dem RGB-Farbraum, während __init__() neben der Definition der Objektattribute auch den Prozess anstößt. Im Folgenden nun ein detaillierter Blick auf die Klasse:
289
10 Bildbearbeitung mit der Python Imaging Library
Listing 10.8: Klassendefinition und Konstruktormethode 001 import Image, ImageDraw, ImageFont 02 import random, string 03 04 class Captcha(object): 05 """ Eine Klasse zur Erzeugung eine Bild-Captcha""" 06 07 def __init__(self, 08 bg_vorlage, 09 c_size=(180,80), 10 captcha_pic = 'captcha.jpg', 11 code_num = 6): 12 # Größe des Captachs 13 self.__c_size = c_size 14 # Bildobjekt für Captcha 15 self.__captcha = Image.new('RGBA',c_size,'white') 16 # zufällige Farbe für Buchstaben 17 self.__color = self.random_color() 18 # Dateiname des Captcha 19 self.__captcha_pic = captcha_pic 20 # Anzahl der Zeichen im Captcha 21 self.__code_num = code_num 22 # Öffnen der Hintergrundvorlage 23 try: 24 self.__bg_vorlage = Image.open(bg_vorlage) 25 except: 26 print 'Bild konnte nicht gefunden werden' 27 return 28 # Aufruf der Methode zur Erzeugung des Captcha 29 self.create_captcha()
Zu Beginn wie gewohnt alle notwendigen Importe, in Zeile 04 folgt dann die Klassendefinition. Die Klasse Captcha erbt von der Klasse objekt. Die Konstruktormethode besitzt vier Parameter, von denen drei mit einem Standardwert versehen sind. Über diese Parameter kann das Captcha-Bild manipuliert werden. Der Parameter bg_vorlage benötigt eine Pfadangabe zu einer Bilddatei, aus der eine Kachel für den Hintergrund des Captcha kopiert wird. Mit c_size lässt sich die Größe des Captcha bestimmen, über captcha_pic lässt sich der Dateiname und der Pfad festlegen, während code_num reguliert, wie viele Codezeichen das Captcha haben wird. Es werden dann die Attribute der Klasse festgelegt. Mit __captcha wird ein Bildobjekt erzeugt, das später durch die Methoden Hintergrund, Linien und Buchstabencode erhält. Die Farbe der Buchstaben und Linien wird durch die Methode random_color() bestimmt und hier bereits dem Attribut __color zugewiesen (Zeile 14). Schließlich wird versucht, die angegebene Hintergrundvorlage zu öffnen. Schlägt dies fehl, wird eine entsprechende Meldung ausgegeben und der Prozess abgebrochen (Zeile 17–21).
290
10.6 Ein Captcha erstellen
Gelingt das Öffnen, wird der Prozess durch Aufruf der Methode create_captcha() angestoßen (Zeile 29). Als Nächstes nun der Blick auf die Methoden random_color() und create_captcha(), die in der Konstruktormethode aufgerufen werden: Listing 10.9: Zufällige Farbe erzeugen und den Prozess anstoßen 31 32 33 34 35 36 37 38 39 40 41 42
def random_color(self): """ Erzeugt zufällig RGB-Werte """ color = tuple([random.choice(range(256)) for x in range(3)]) return color def create_captcha(self): """ Erzeugen und Sichern des Captcha """ self.create_bg() self.draw_lines() self.write_code() self.__captcha.save(self.__captcha_pic)
Für das Erzeugen der Zufallsfarbe wird dreimal eine Zufallszahl zwischen 0 und 255 erzeugt und in eine Liste geschrieben. Diese wird zu einem Tupel gewandelt (Zeile 33–34) und per return-Anweisung zurückgegeben. Da der Hintergrund des CaptchaBilds in Graustufen gewandelt wird, könnte es theoretisch passieren, dass die Methode einen Grauwert erzeugt, der sich nicht mehr genügend vom Hintergrund abhebt. Da aber jede der drei Zahlen per Zufallsgenerator erzeugt wird, ist die Wahrscheinlichkeit, dass drei gleiche Zahlen dabei herauskommen – was ja einen Grauwert bedeuten würde –, äußerst gering. Die Methode create_captcha() ruft die drei Methoden auf, die für die Erstellung des Bildinhalts verantwortlich sind, und speichert am Ende das Bild auf den Datenträger (Zeile 42). Den Anfang beim Bildinhalt macht die Methode create_bg(), die den Bildhintergrund erstellt: Listing 10.10: Erzeugung eines gekachelten Hintergrunds 44 45 46 47 48 49 50 51 52 53
def create_bg(self): """Erstellt einen gekachelten Hintergrund aus einer zufällig gewählten Kachel""" # Kachelgröße kachel_dim = 10,10 # Abmessung von Captachbild und Kachel als Einzelwerte mw, mh = self.__c_size kw, kh = kachel_dim # Zufälligen Ausschnitt aus Vorlage kopieren
291
10 Bildbearbeitung mit der Python Imaging Library
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
w,h = self.__bg_vorlage.size l = random.choice(range(w-kw)) r = l + kw o = random.choice(range(h-kh)) u = o +kh kachel = self.__bg_vorlage.crop((l,o,r,u)) # Kachel in Captcha einfügen for x in range(mh/kh+1): for y in range(mw/kw+1): self.__captcha.paste(kachel,(y*kw,x*kh)) # Farbinformation eliminieren self.__captcha = self.__captcha.convert('L') # In RGB zurücktransformieren self.__captcha = self.__captcha.convert('RGBA')
Dies geschieht nach der gleichen Methode, wie bereits in Abschnitt 10.3 gezeigt. Die Kachel wird an zufälliger Position aus dem Vorlagenbild gewonnen (Zeile 53–59) und dann regelmäßig in das Captcha-Bild eingefügt (Zeile 62–64). Anschließend wird das Bild in Graustufen umgewandelt, um es danach gleich wieder in den RGB-Modus zu transferieren (Zeile 66–68). Damit hat der gekachelte Hintergrund jede Farbinformation verloren, das Bild kann aber weiterhin farbig weiterbearbeitet werden. Nun wird ein zufälliges Linienmuster auf das Bild gebracht: Listing 10.11: Zufallslinien erzeugen 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
292
def draw_lines(self): """ Zeichnet zufällige Linien in das Bild""" # Zeichnenobjekt erzeugen zeichnen = ImageDraw.Draw(self.__captcha) # Abmessungen des Captcha als Einzelwerte b,h = self.__c_size # je acht Linien vertikal u. horizontal zeichnen for i in range(8): Anfangs- und Enpunkte zufällig bestimmen start_x = random.choice(range(b+1)) start_y = random.choice(range(h+1)) end_x = random.choice(range(b+1)) end_y = random.choice(range(h+1)) # Linien zeichnen zeichnen.line(((start_x,0), (end_x, h)), width=1, fill=self.__color) zeichnen.line(((0,start_y), (b,end_y)), width=1, fill=self.__color)
10.6 Ein Captcha erstellen
Es werden je acht Linien von oben nach unten sowie von links nach rechts gezogen, so dass insgesamt 16 Linien über das Bild laufen. Bei den Linien, die in vertikaler Richtung gezogen werden, wird die horizontale Koordinate zufällig bestimmt, bei den Linien, die horizontal verlaufen, die vertikale. Damit zieht sich jede Linie über die gesamte Bildbreite bzw. -höhe, die genaue Linienführung bleibt aber zufallsbestimmt (Zeilen 79–89). Über die Linien wird dann der Buchstabencode geschrieben: Listing 10.12: Den Buchstabencode ins Bild bringen 91 def write_code(self): 92 """ Schreibt einen Buchstabencode ins Bild """ 93 # Buchstabenpool 94 letter_pool = string.ascii_letters 95 # Schrift festlegen 96 font = ImageFont.truetype("timesbd.ttf", 28) 97 # Zwischenbild erzeugen 98 layer = Image.new('RGBA',self.__c_size,(0,0,0,0)) 99 # Anfangswert für horizontale Position festlegen 100 offset = 5 101 # Buchstaben schreiben 102 for x in range(self.__code_num): 103 # Zufallswahl des Buchstaben 104 bst = random.choice(letter_pool) 105 # Abmessungen des Buchstabens ermitteln 106 bst_size = font.getsize(bst) 107 # neues Bildobjekt für Buchstaben 108 bst_pic = Image.new('RGBA',bst_size,(0,0,0,0)) 109 # Buchstaben zeichnen 110 zeichnen = ImageDraw.Draw(bst_pic) 111 zeichnen.text((0, 0), bst, font=font,fill=self.__color) 112 # Buchstaben zufällig drehen 113 bst_pic = bst_pic.rotate(random.choice(range(-30,31)), 114 expand=1) 115 # Buchstaben in Zwischenbild einfügen 116 layer.paste(bst_pic, 117 (offset,random.choice(range(10,self.__c_size[1]118 bst_pic.size[1])))) 119 # horizontale Position für folgenden Buchstaben 120 offset += bst_pic.size[0] 121 # Zwischenbild in Captcha-Bild einfügen 122 self.__captcha =Image.composite(layer,self.__captcha, layer)
Das Schreiben der Buchstaben erfolgt nach dem Prinzip, wie in Abschnitt 10.5 beschrieben. Sie werden allerdings nicht direkt in das Captcha-Bild geschrieben, sondern zunächst wird jeder Buchstabe in ein eigenes Bild geschrieben. Der Grund dafür liegt darin, dass die Buchstaben um einen zufälligen Winkel gedreht werden sollen,
293
10 Bildbearbeitung mit der Python Imaging Library
bevor sie ins Bild eingefügt werden (Zeile 113). Die gedrehten Buchstaben werden in ein Zwischenbild eingefügt, das in Zeile 98 erzeugt wurde. Dies ist notwendig, weil für das Einfügen der Buchstaben in das Captcha-Bild die Methode composite() verwendet wird. Mit ihr ist es möglich, zwei Bilder anhand einer Maske zusammenzufügen. Die zwei Bilder sind das Captcha-Bild und das Zwischenbild. Als Maske wird aber ebenfalls das Zwischenbild verwendet, so dass nur die Buchstaben in das Captcha-Bild übertragen werden. Dieser kleine Trick macht es möglich, gedrehte Buchstaben in das Captcha-Bild einzufügen, ohne deren Umgebungen mitzunehmen. Zum Erstellen des Captcha reicht es nun aus, die Klasse aufzurufen und ihr den Pfad zu einem Vorlagenbild zu übergeben: >>> captcha = Captcha('vorlage.jpg')
Es wird ein Bild mit den Standardwerten, wie sie in der Konstruktormethode aufgeführt sind, erzeugt. Dieses trägt den Namen captcha.jpg und findet sich im gleichen Verzeichnis wie das Modul. Aussehen könnte es wie in der folgenden Abbildung:
Abbildung 10.4: Erzeugtes Captcha-Bild Diese Captcha-Klasse erzeugt nur ein Bild. Für eine Verwendung in einer Webapplikation müsste sie natürlich auch den Buchstabencode speichern, um ihn mit der Benutzereingabe abgleichen zu können.
294
A Eingebaute Funktionen Die folgende Tabelle listet die im Python-Hauptmodul implementierten Funktionen auf. Sie sind jederzeit direkt aufrufbar. Methode
Erläuterung
abs(zahl)
Gibt den absoluten Wert für zahl zurück. zahl kann eine Ganzzahl oder eine Fließkommazahl sein.
all(sequenz)
Gibt True zurück, wenn alle Elemente von sequenz True sind.
any(sequenz)
Gibt True zurück, wenn ein Element von sequenz True ist.
basestring()
Superklasse (übergeordnete Klasse) für str und unicode, die nicht aufgerufen oder instanziert werden kann. Wird genutzt, um zu testen, ob ein Objekt eine Instanz von str oder unicode ist.
bool([zahl])
Konvertiert zahl zu einem Bool-Wert. Wird kein Wert angegeben, gibt die Funktion False zurück.
callable(objekt)
Gibt True zurück, wenn objekt aufrufbar ist.
chr(zahl)
Gibt das Zeichen zurück, dessen ASCII-Codewert zahl ist. zahl muss zwischen 0 und 255 liegen
classmethod(funktion)
Gibt für funktion die Klassenmethode zurück.
cmp(x, y)
Vergleicht die beiden Objekte x und y und gibt das Ergebnis als Integer zurück. Der Rückgabewert ist negativ wenn xy.
Gibt True zurück, wenn objekt eine Instanz von classb ist.
issubclass(classa,classb)
Gibt True zurück, wenn classa eine Unterklasse von classb ist.
iter(o[, sentinel])
Gibt ein iterierbares Objekt wieder; o muss dabei eine Sequenz sein.
len(s)
Gibt die Anzahl der Elemente eines Objekts wieder. s kann eine Sequenz oder ein Dictionary sein.
list([sequenz])
Gibt eine Liste zurück, in der die Elemente und deren Anordnung dieselben sind, wie die von sequenz.
locals()
Gibt den lokalen Namensraum wieder.
long([x[, radix]])
Konvertiert eine Zeichenkette oder eine Zahl in eine lange Integerzahl.
map(funktion,list, ...)
Wendet funktion auf jedes Element von list an und gibt eine Liste mit den Ergebnissen zurück.
max(s[, args...][key])
Gibt das größte Element einer nicht leeren Sequenz wieder.
min(s[, args...][key])
Gibt das kleinste Element einer nicht leeren Sequenz wieder.
objekt()
Gibt ein neues leeres Objekt zurück.
oct(x)
Konvertiert eine Integerzahl in eine Oktalzahl.
open(filename[,mode[, bufsize]])
Öffnet eine Datei mit dem Dateinamen filename und gibt ein Objekt dieser Datei zurück.
ord(c)
Gibt die Ordnungszahl der Unicode-Tabelle für das Element c wieder. Diese Ordnungszahl wird als String ausgegeben und muss zwischen 0 und 65535 liegen.
pow(x, y[, z])
Gibt das Ergebnis von xy wieder. Ist z angegeben, wird das Ergebnis von x hoch y %(modulo) z zurückgegeben.
property([fget[,fset[,fdel[, doc]]]])
Gibt ein Eigenschaftenattribut für ein Klasse wieder, die direkt von object erbt.
range([start,]stop[,step])
Gibt eine Liste von Ganzzahlen, von start bis stop aus. step bezieht sich auf die Schrittweite und ist standardmäßig auf 1 gesetzt. Wird start weggelassen, wird als Voreinstellung der Wert 0 verwendet.
Die Funktion wandelt die Eingabezeile in einen String um und gibt diesen zurück. Wird prompt angeben, wird es vor die Standardeingabe geschrieben.
reduce(funktion,sequenz[, initializer])
Wendet funktion auf zwei Argumente kumulativ zu den Elementen von sequenz an.
reload(module)
Lädt ein bereits geladenes Modul erneut.
repr(objekt)
Gibt eine Zeichenkette zurück, die eine repräsentierfähige Version von objekt enthält.
reversed(seq)
Gibt eine Sequenz zurück, die die umgekehrte Reihenfolge von seq enthält.
round(x[, n])
Gibt die Fließkommazahl x gerundet auf n Stellen nach dem Komma zurück.
set([sequenz])
Gibt ein set zurück, dessen Elemente von sequenz übernommen werden.
setattr(objekt,name,wert)
Ändert den Wert des Attributs name von objekt in wert. Ist das Attribut nicht vorhanden, wird es erstellt.
slice([start,]stop[,step])
Gibt ein Slicing-Objekt zurück, das die Werte von start bis stop enthält. step gibt die Schrittweite an.
sorted(sequenz[,cmp[,key[, reverse]]])
Gibt eine geordnete Liste mit den Elementen von sequenz zurück. Dabei gibt cmp die Vergleichsfunktion an, key spezifiziert die Schlüsselfunktion und reverse gibt die Sortierreihenfolge an.
staticmethod(funktion)
Gibt die statische Methode für funktion zurück.
str([objekt])
Gibt einen String zurück, der die druckfähige Form von objekt repräsentiert.
sum(sequence[,start])
Gibt die Summe aller Elemente von sequenz wieder. Wird für start ein Wert angegeben, wird die Summation bei diesem Element begonnen.
super(typ[,objekt-oder-typ])
Gibt die Superklasse von type zurück.
tuple([sequence])
Gibt ein Tuple zurück, dessen Elemente und deren Reihenfolge dieselben sind wie die von sequenz.
type(objekt)
Gibt den Typ von objekt zurück.
type(name,bases,dict)
Gibt ein neues Typobjekt zurück. name gibt den Klassennamen an, das bases-Tuple die Basisklasse und dict das Dictionary für die Namensräume.
unichr(zahl)
Gibt das Zeichen zurück, dessen Unicode-Wert zahl ist. zahl muss zwischen 0 und 0xFFFF (UCS2) oder 0 und 0x10FFFF ( UCS4) liegen.
Gibt die Unicode-Schreibweise von objekt als Zeichenkette wieder.
vars([objekt])
Gibt ein Dictionary entsprechend der aktuellen lokalen Zeichentabelle zurück.
xrange([start,]stop[,step])
Vergleichbar mit range(), gibt aber keine Liste, sondern ein xrange-Objekt zurück. Das ist eine Sequenz, die dieselben Werte wie die entsprechende Liste enthält, ohne diese Werte aber gleichzeitig zu speichern.
zip([sequenz, ...])
Die Funktion gibt eine Liste von Tuples zurück, wobei das n-te Tupel das n-te Element jeder Sequenz enthält.
B Built-in Exceptions Die folgende Tabelle listet alle eingebauten Exceptions auf: Exception
Erläuterung
BaseException
Die Basisklasse für alle eingebauten Exceptions
Exception
Alle eingebauten, nicht systembeendenden Exceptions erben von dieser Klasse. Alle vom Benutzer erstellten Ausnahmen sollten von dieser Klasse erben.
StandardError
Die Basisklasse für alle eingebauten Exceptions außer StopIteration, GeneratorExit, KeyboardInterrupt und SystemExit. Klasse erbt selbst von Exception.
ArithmeticError
Basisklasse für alle arithmetischen Ausnahmen: OverflowError, ZeroDivisionError, FloatingPointError.
LookupError
Basisklasse für die Ausnahmen, die durch ungültige Schlüssel oder Index verursacht wurden: IndexError, KeyError.
EnvironmentError
Basisklasse für alle Ausnahmen die sich außerhalb des PythonSystems ereignen: IOError, OSError.
AssertionError
Tritt auf, wenn eine Zuweisung fehlschlägt.
AttributeError
Tritt auf, wenn eine Attributreferenz oder eine Attributzuweisung fehlschlägt. (Wenn ein Objekt Attributreferenzen oder Attributzuweisungen überhaupt nicht unterstützt, tritt ein TypeError auf.)
EOFError
Tritt auf, wenn die Funktionen input() oder raw_input() eine endof-file-Bedingung (Test, ob das Ende einer Datei oder eines Datenstroms erreicht ist) verletzen.
FloatingPointError
Wird durch eine ungültige Operation mit Fließkommazahlen aufgerufen.
GeneratorExit
Wird durch die close()-Methode aufgerufen.
IOError
Tritt auf, wenn ein I/O-Vorgang (z. B. print, open()) fehlschlägt (z. B. 'file not found' oder 'disk full').
ImportError
Wird von einer ungültigen import-Anweisung aufgerufen.
Tabelle B.1: Liste der eingebauten Fehlertypen
B Built-in Exceptions
Exception
Erläuterung
IndexError
Tritt auf, wenn eine Sequenzindexierung außerhalb des Gültigkeitsbereichs liegt.
KeyError
Tritt dadurch auf, dass ein Schlüssel nicht im Set von existierenden Schlüsseln gefunden wird.
KeyboardInterrupt
Tritt durch einen Benutzerabbruch (Strg)+(C) auf.
MemoryError
Wird dadurch aufgerufen, dass durch eine Operation ein Speicherüberlauf stattfindet.
NameError
Wenn ein lokaler oder globaler Name nicht gefunden wird, tritt dieser Fehler auf.
NotImplementedError
Erbt von RuntimeError und wird durch abstrakte Methoden aufgerufen, die abgeleitete Klassen benötigen, um deren Methoden zu überschreiben.
OSError
Diese Klasse ist abgeleitet von EnvironmentError und wird hauptsächlich für die os.error-Ausnahme des os-Moduls gebraucht. Nähere Beschreibung siehe EnvironmentError.
OverflowError
Wird durch eine arithmetische Operation aufgerufen, deren Ergebnis zu groß zum Anzeigen ist. Das gilt nicht für long integers.
ReferenceError
Diese Ausnahme wird ausgelöst, wenn ein schwach referenziertes Objekt aufgerufen werden soll, das bereits vom Garbage Collector aus dem Arbeitsspeicher entfernt wurde. Schwach referenzierte Objekte werden über das Modul weakref gebildet. Sie können vom Garbage Collector aus dem Arbeitsspeicher entfernt werden, auch wenn noch eine Referenz auf sie besteht. Sinnvoll ist ihr Einsatz z. B. dann, wenn man ein Programm entwickelt, das zwei oder mehrere voneinander unabhängige Bereiche besitzt. Wechselt der Benutzer von einem Bereich in den anderen, können schwach referenzierte Objekte aus dem ersten Bereich aus dem Arbeitsspeicher entfernt werden.
RuntimeError
Tritt bei Fehlern auf, die nicht in eine andere Kategorie fallen.
StopIteration
Wird durch die next()-Methode eines Iterators aufgerufen, um deutlich zu machen, dass es keine weiteren Werte gibt.
SyntaxError
Tritt auf, wenn ein Syntaxfehler vorliegt.
SystemError
Wird durch den Interpreter aufgerufen, wenn dieser auf einen internen Fehler trifft.
SystemExit
Dieser Abbruch wird durch die sys.exit() aufgerufen. Wird dieser Abbruch nicht behandelt, bricht der Python-Interpreter das Programm ab.
TypeError
Tritt auf, wenn eine Operation oder eine Funktion ein Objekt mit einem unpassenden Typ verarbeiten muss.
Tabelle B.1: Liste der eingebauten Fehlertypen (Forts.)
302
B Built-in Exceptions
Exception
Erläuterung
UnboundLocalError
Tritt auf, wenn in einer Funktion oder Methode eine Referenz von einer lokalen Variablen gemacht wurde, dieser aber kein Wert zugewiesen wurde.
UnicodeError
Wird durch Fehler ausgelöst, die bei Kodierungen und Dekodierungen von Unicode-Zeichenketten entstehen.
UnicodeEncodeError
Wird durch Unicode-bezogene Kodierungen aufgerufen, wenn Fehler entstehen.
UnicodeDecodeError
Wird durch Unicode-bezogene Dekodierungen aufgerufen, wenn Fehler entstehen.
UnicodeTranslateError
Wird durch einen Unicode-bezogenen Fehler während der Übersetzung verursacht.
ValueError
Dieser Fehler wird dadurch aufgerufen, dass eine Operation oder Funktion ein Argument erhält, dass zwar den richtigen Typ, aber einen unpassenden Wert hat. Tritt auf, wenn es für diesen Fall keine präzisere Exception wie zum Beispiel IndexError gibt.
WindowsError
Wenn ein Windows-spezifischer Fehler auftritt oder wenn die Error-Nummer nicht einer errno entspricht, tritt dieser Fehler auf.
ZeroDivisionError
Wird bei einer Division oder Modulooperation durch 0 aufgerufen.
Warning
Basisklasse für alle Warnungen
UserWarning
Basisklasse für Warnungen, die durch Benutzercode entstehen
DeprecationWarning
Basisklasse für Warnungen über veraltete Funktionen
PendingDeprecationWarning
Basisklasse für Warnungen über Funktionen, die in der Zukunft veraltet sein werden
SyntaxWarning
Basisklasse für Warnungen, die durch fragwürdige Syntax entstehen
RuntimeWarning
Basisklasse für Warnungen über fragwürdiges Laufzeitverhalten
exception FutureWarning
Basisklasse für Warnungen über Konstrukte die sich in der Zukunft semantisch verändern
ImportWarning
Basisklasse für Warnungen über mögliche Fehler bei Importen von Modulen
UnicodeWarning
Basisklasse für Warnungen, die sich auf Unicode beziehen
Tabelle B.1: Liste der eingebauten Fehlertypen (Forts.)
303
C
Link-Verzeichnis
Neben der Python-Hauptseite (http://www.python.org/) gibt es zahlreiche Seiten, die Artikel, Software, Tutorials oder anderes anbieten. Hier eine sortierte Auswahl:
Link-Listen Link
Beschreibung
Sprache
http://jtauber.com/python
Link-Liste für Python-Tools
ENG
http://divmod.org/trac/wiki
Link-Liste für Python-Tools und Frameworks
ENG
http://zephyrfalcon.org/labs/
Im Download-Bereich befinden sich mehrere Python-Anwendungen.
D Webhoster mit mod_python Viele Webhoster bieten Python als Sprache für CGI-Skripte an. Leider sind es noch eher wenige, die das Apache-Modul mod_python anbieten. Um nicht lange suchen zu müssen, ist hier eine Liste zusammengestellt: Webspacestar – http://www.webspacestar.de/ EW-Hosting – http://www-ew-hosting.de/ crea-IT – http://www.crea-it.de/ BeKaWe Media – http://www.bekawe-hosting.de/ Thüringer Webhosting – http://www.thnetz.de domainFACTORY – http://www.df.eu Nerionics – http://www.nerionics.de/ Freestyle Media Service – http://www.freestyle-media.de/ 3W-Service – http://w3w.de/
Copyright Daten, Texte, Design und Grafiken dieses eBooks, sowie die eventuell angebotenen eBook-Zusatzdaten sind urheberrechtlich geschützt. Dieses eBook stellen wir lediglich als persönliche Einzelplatz-Lizenz zur Verfügung! Jede andere Verwendung dieses eBooks oder zugehöriger Materialien und Informationen, einschliesslich •
der Reproduktion,
•
der Weitergabe,
•
des Weitervertriebs,
•
der Platzierung im Internet, in Intranets, in Extranets,
•
der Veränderung,
•
des Weiterverkaufs
•
und der Veröffentlichung
bedarf der schriftlichen Genehmigung des Verlags. Insbesondere ist die Entfernung oder Änderung des vom Verlag vergebenen Passwortschutzes ausdrücklich untersagt! Bei Fragen zu diesem Thema wenden Sie sich bitte an: [email protected] Zusatzdaten Möglicherweise liegt dem gedruckten Buch eine CD-ROM mit Zusatzdaten bei. Die Zurverfügungstellung dieser Daten auf unseren Websites ist eine freiwillige Leistung des Verlags. Der Rechtsweg ist ausgeschlossen. Hinweis Dieses und viele weitere eBooks können Sie rund um die Uhr und legal auf unserer Website