Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
1 Einleitung
2 Überblick über Python
3 Die Arbeit mit Python
4 Der interaktive Modus
5 Grundlegendes zu Python-Programmen
6 Kontrollstrukturen
7 Das Laufzeitmodell
8 Basisdatentypen
9 Benutzerinteraktion und Dateizugriff
10 Funktionen
11 Modularisierung
12 Objektorientierung
13 Weitere Spracheigenschaften
14 Mathematik
15 Strings
16 Datum und Zeit
17 Schnittstelle zum Betriebssystem
18 Parallele Programmierung
19 Datenspeicherung
20 Netzwerkkommunikation
21 Debugging
22 Distribution von Python-Projekten
23 Optimierung
24 Grafische Benutzeroberflächen
25 Python als serverseitige Programmiersprache im WWW mit Django
26 Anbindung an andere Programmiersprachen
27 Insiderwissen
28 Zukunft von Python
A Anhang
Stichwort

Download:
- ZIP, ca. 4,8 MB
Buch bestellen
Ihre Meinung?

Spacer
 <<   zurück
Python von Peter Kaiser, Johannes Ernesti
Das umfassende Handbuch - Aktuell zu Python 2.5
Buch: Python

Python
gebunden, mit CD
819 S., 39,90 Euro
Galileo Computing
ISBN 978-3-8362-1110-9
Pfeil 13 Weitere Spracheigenschaften
  Pfeil 13.1 Exception Handling
    Pfeil 13.1.1 Eingebaute Exceptions
    Pfeil 13.1.2 Werfen einer Exception
    Pfeil 13.1.3 Abfangen einer Exception
    Pfeil 13.1.4 Eigene Exceptions
    Pfeil 13.1.5 Erneutes Werfen einer Exception
  Pfeil 13.2 List Comprehensions
  Pfeil 13.3 Docstrings
  Pfeil 13.4 Generatoren
  Pfeil 13.5 Iteratoren
  Pfeil 13.6 Interpreter im Interpreter
  Pfeil 13.7 Geplante Sprachelemente
  Pfeil 13.8 Die with-Anweisung
  Pfeil 13.9 Function Decorator
  Pfeil 13.10 assert
  Pfeil 13.11 Weitere Aspekte der Syntax
    Pfeil 13.11.1 Umbrechen langer Zeilen
    Pfeil 13.11.2 Zusammenfügen mehrerer Zeilen
    Pfeil 13.11.3 String conversions


Galileo Computing - Zum Seitenanfang

13.4 Generatoren  topZur vorigen Überschrift

In diesem Abschnitt werden wir uns mit dem Konzept der Generatoren beschäftigen, die eine komfortable Möglichkeit anbieten, Reihen von Werten zu verarbeiten. Weil sich das noch sehr abstrakt anhört, wollen wir direkt mit einem Beispiel beginnen. Sie erinnern sich sicherlich noch an die Built-in Function range, die im Zusammenhang mit for-Schleifen eine wichtige Rolle spielt:

>>> for i in range(10): 
        print i, 
0 1 2 3 4 5 6 7 8 9

Wie wir bereits wissen, gibt range eine list-Instanz zurück, die in diesem Fall die ganzen Zahlen von 0 bis 9 enthält. Über diese Liste können wir dann mittels der for-Schleife iterieren und erhalten die Ausgabe aller Ziffern. Für kleine Listen ist dieses Vorgehen auch vollkommen angemessen, allerdings wird es höchst ineffizient, wenn man sehr große Listen durchlaufen möchte, weil erst die komplette Liste im Speicher aufgebaut werden muss. Wir benötigen aber eigentlich pro Schleifendurchlauf nur eine einzige Zahl im Arbeitsspeicher, nämlich den aktuellen Zähler. Die gesamte Liste brauchen wir zu keiner Zeit.

Nun könnte man auf die Idee kommen, das Problem mithilfe einer while-Schleife zu umgehen, indem man den Zähler manuell verwaltet:

>>> i = 0 
>>> while i < 10: 
        print i, 
        i += 1

Dieses Vorgehen, bei dem man um Pythons eigene for-Schleife »herumprogrammiert«, ist wenig elegant und widerspricht außerdem Pythons Grundsatz, klare und deshalb gut lesbare Programme zu schreiben. Allerdings würden weder Rechenzeit noch Speicherplatz für die Erzeugung der unnötigen Liste vergeudet.

Wir wünschen uns eine Technik, die die Lesbarkeit einer normalen for-Schleife mit der Effizienz der while-Schleife aus dem letzten Beispiel vereint.

An dieser Stelle kommen die sogenannten Generatoren ins Spiel. Ein Generator ist eine Funktion, die bei jedem Aufruf das nächste Element einer virtuellen Sequenz zurückgibt. Für unser Beispiel bräuchten wir also einen Generator, der nacheinander alle zehn Ziffern zurückgibt. Die Definition dieser auch Generatorfunktionen genannten Konstrukte ist der von normalen Funktionen sehr ähnlich. Der von uns benötigte Generator, wir nennen ihn range_generator, lässt sich folgendermaßen implementieren (wundern Sie sich bitte nicht über das »yield«, es wird im Anschluss erklärt):

def range_generator(max): 
    i = 0 
    while i < max: 
        yield i 
        i += 1

Sie werden nun sicherlich sagen, dass wir einfach die ungeschickte while-Schleife in eine Funktion verpackt haben und diese Lösung dadurch immer noch wenig elegant ist. Der Unterschied zu der vorhergehenden while-Schleife besteht aber darin, dass wir mithilfe von unserem neuen Generator namens range_generator elegante und gleichzeitig schnelle for-Schleifen schreiben können:

>>> for i in range_generator(10): 
        print i, 
0 1 2 3 4 5 6 7 8 9

Der Funktionsaufruf range_generator(10) gibt ein iterierbares Objekt (die generator-Instanz) zurück, das mit einer for-Schleife durchlaufen werden kann. Wir müssen also nur einmal mit der while-Notlösung arbeiten und können anschließend ganz normale for-Schleifen benutzen, wodurch unser Code sehr viel besser lesbar und schneller wird.

Der Knackpunkt bei Generatoren liegt in dem yield-Statement, mit dem wir die einzelnen Werte der virtuellen Sequenz zurückgeben. Die Syntax von yield unterscheidet sich dabei nicht von der des return-Statements und muss deshalb nicht weiter erläutert werden. Entscheidend ist, wie yield sich im Vergleich zu return auf die Verarbeitung des Programms auswirkt.

Wird in einer normalen Funktion während eines Programmlaufs ein return erreicht, wird der Kontrollfluss an die nächsthöhere Ebene zurückgegeben und der Funktionslauf beendet. Außerdem werden alle lokalen Variablen der Funktion wieder freigegeben. Bei einem erneuten Aufruf der Funktion würde Python wieder ganz am Anfang der Funktion beginnen und die komplette Funktion erneut ausführen.

Im Gegensatz dazu werden beim Erreichen einer yield-Anweisung die aktuelle Position innerhalb der Generatorfunktion und ihre lokalen Variablen gespeichert, und es erfolgt ein Rücksprung in das aufrufende Programm mit dem hinter yield angegebenen Wert. Beim nächsten Iteratoraufruf macht Python dann hinter dem zuletzt ausgeführten yield weiter und kann wieder auf die alten lokalen Variablen, in dem Fall i, zugreifen. Erst wenn das Ende der Funktion erreicht wird, beginnen die endgültigen Aufräumarbeiten.

Weil die oben beschriebene Generatorfunktion range_generator äußerst oft gebraucht wird, ist sie bereits in Python unter dem Namen xrange vordefiniert. Die Funktion xrange erwartet die gleichen Parameter wie range und verhält sich auch sonst genauso – mit der einzigen Ausnahme, dass sie eben keine echte Liste erzeugt, sondern die Elemente nacheinander berechnet. Für Ihre zukünftigen for-Schleifen empfehlen wir Ihnen deshalb immer, xrange zu verwenden.

>>> for i in xrange(2, 5): 
        print i, i*i, i**3, i**4 
2 4 8 16 
3 9 27 81 
4 16 64 256

Generatoren sind sehr flexibel und können durchaus mehrere yield-Anweisungen enthalten:

def generator_mit_mehreren_yields(): 
    a = 10 
    yield a 
    yield a*2 
    b = 5 
    yield a+b

Auch dieser Generator kann mit einer for-Schleife durchlaufen werden:

>>> for i in generator_mit_mehreren_yields(): 
        print i, 
10 20 15

Im ersten Iterationsschritt wird die lokale Variable a in der Generatorfunktion angelegt und ihr Wert dann mittels yield a an die Schleife übergeben. Beim nächsten Schleifendurchlauf wird dann bei yield a*2 weitergemacht, wobei die zurückgegebene 20 zeigt, dass der Wert von a tatsächlich zwischen den Aufrufen erhalten geblieben ist. Während des letzten Iterationsschritts erzeugen wir zusätzlich die lokale Variable b mit dem Wert 5 und geben die Summe von a und b an die Schleife weiter, wodurch die 15 ausgegeben wird. Da nun das Ende der Generatorfunktion erreicht ist, bricht die Schleife nach drei Durchläufen ab.

Es ist auch möglich, eine Generatorfunktion frühzeitig zu verlassen, wenn dies erforderlich sein sollte. Um dies zu erreichen, benutzt man das return-Statement ohne Rückgabewert. Der folgende Generator erzeugt abhängig vom Wert des optionalen Parameters auch_jungen eine Folge aus zwei Mädchennamen oder zwei Mädchen- und Jungennamen:

def namen(auch_jungen=True): 
    yield "Sonja" 
    yield "Lisa" 
    if not auch_jungen: 
        return 
    yield "Florian" 
    yield "Jan"

Mithilfe von der Built-in Function list können wir aus den Werten des Generators eine Liste erstellen, die entweder nur "Sonja" und "Lisa" oder zusätzlich "Florian" und "Jan" enthält:

>>> list(namen()) 
['Sonja', 'Lisa', 'Florian', 'Jan'] 
>>> list(namen(False)) 
['Sonja', 'Lisa']

Generator Expressions

Sie erinnern sich sicherlich noch an die sogenannten List Comprehensions, mit denen Sie auf einfache Weise Listen erzeugen konnten. Mit solchen List Comprehensions konnten Sie beispielsweise eine Liste mit den ersten zehn Quadratzahlen erzeugen:

>>> [i*i for i in range(1, 11)] 
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Wenn wir nun die Summe dieser ersten zehn Quadratzahlen bestimmen wollten, könnten wir das mithilfe der Built-in-Function sum erreichen, indem wir schreiben:

>>> sum([i*i for i in range(1, 11)]) 
385

So weit, so gut. Allerdings wurde hier wieder eine nicht benötigte list-Instanz erzeugt, die Speicherplatz vergeudet.

Um auch in solchen Fällen nicht auf den Komfort von List Comprehensions verzichten zu müssen, wurden sogenannte Generator Expressions eingeführt. Generator Expressions sehen genauso aus wie die entsprechenden List Comprehensions, mit der Ausnahme, dass statt der eckigen Klammern [] die runden Klammern () als Begrenzung verwendet werden. Damit können wir das obige Beispiel speicherschonend mit einer Generator Expression formulieren:

>>> sum((i*i for i in range(1, 11))) 
385

Die umschließenden runden Klammern können entfallen, wenn der Ausdruck sowieso schon geklammert ist. In unserem sum-Beispiel können wir also ein Klammerpaar entfernen:

>>> sum(i*i for i in range(1, 11)) 
385

Ein wichtiger Unterschied zu List Comprehensions betrifft die Seiteneffekte. Betrachten wir mal das folgende Beispiel, das List Comprehensions benutzt:

>>> x = "Ob ich wohl meinen Wert behalte?" 
>>> sum([x*x for x in range(1, 11)]) 
385 
>>> x 
10

Wie Sie sehen, wurde der Wert der globalen Variablen x durch den Zähler der List Comprehension überschrieben. Benutzen wir einen Generator, um die Quadratzahlen zu erzeugen, ergibt sich folgendes Bild:

>>> x = "Ob ich wohl meinen Wert behalte?" 
>>> sum(x*x for x in range(1, 11)) 
385 
>>> x 
'Ob ich wohl meinen Wert behalte?'

Hier wurde das globale x nicht angetastet und bei seinem Anfangswert belassen. Sie sollten sich dieser möglichen Seiteneffekte bei List Comprehensions bewusst sein, gerade dann, wenn Sie eine vorhandene Generator Expression durch eine List Comprehension ersetzen möchten. [Dieses merkwürdige Verhalten kommt daher, dass die Python-Entwickler beim Einführen der List-Comprehension etwas unachtsam gewesen sind und solche Seiteneffekte zugelassen haben. Als man dies bemerkte, konnte man es nicht mehr ändern, da bereits geschriebene Programme durch eine entsprechende Änderung unter Umständen nicht mehr korrekt arbeiten würden. Erst mit Python 3000, das Ende 2008 erscheint, wird dieser Mangel beseitigt. Die später erst in die Sprache aufgenommenen Generator-Expressions wurden von Anfang an ohne gefährliche Seiteneffekte eingeführt. ]

Generatoren können Ihnen helfen, Ihre Programme sowohl in der Lesbarkeit als auch hinsichtlich der Ausführungsgeschwindigkeit zu verbessern. Immer dann, wenn Sie es mit einer komplizierten und dadurch schlecht lesbaren while-Schleife zu tun haben, sollten Sie prüfen, ob ein Generator die Aufgabe nicht eleganter übernehmen kann.

Wir haben uns in diesem Kapitel auf die Definition von Generatoren und ihre Anwendung in der for-Schleife oder mit list beschränkt. Im folgenden Abschnitt werden Sie die Hintergründe und die technische Umsetzung kennenlernen, denn hinter den Generatoren und der for-Schleife steht das Konzept der Iteratoren.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.






 <<   zurück
  
  Zum Katalog
Zum Katalog: Python






Python
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Linux






 Linux


Zum Katalog: Ubuntu GNU/Linux






 Ubuntu GNU/Linux


Zum Katalog: Praxisbuch Web 2.0






 Praxisbuch Web 2.0


Zum Katalog: UML 2.0






 UML 2.0


Zum Katalog: Praxisbuch Objektorientierung






 Praxisbuch Objektorientierung


Zum Katalog: Einstieg in SQL






 Einstieg in SQL


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für Fachinformatiker


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Galileo Press 2008
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de