Beispiele für Realworld Use Cases für Generatoren

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
LanX
User
Beiträge: 92
Registriert: Samstag 20. Februar 2010, 12:46

Hi

Python bietet doch die Möglichkeit mittels

Code: Alles auswählen

yield
Continuations zu erzeugen.

Ich würde gerne dazu etwas präsentieren, aber leider sind alle usecases die ich bisher gesehen habe aus dem Mathematischen Bereich (z.B. Iterator der unendlich viele Primzahlen nacheinander liefert usw.)

Meine Zielgruppe kommt aber aus der Praxis wo Project Euler Probleme im wahrsten Sinne des Wortes akademisch sind.

Kann mir da jemand schicke Anwendungsfälle zeigen, wo sich Code dank Generatoren vereinfacht?

(Das einzige was mir trotz längerem Grübeln Einfiel war einen Eingabestream mit mehreren Generatoren sukzessive aufzusplitten, also z.B. ein HTML-File -> Tabellen -> Zeilen -> Elemente)

Gruß
Rolf
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

LanX hat geschrieben: Kann mir da jemand schicke Anwendungsfälle zeigen, wo sich Code dank Generatoren vereinfacht?
Ist das denn wirklich das primäre Ziel von Generatoren? Ich war bisher davon ausgegangen, dass es primär um das Sparen von Speicher und Prozessorlast geht. Das nennt zumindest Leonidas auch in seinen Folien zum diesem Thema.
(Das einzige was mir trotz längerem Grübeln Einfiel war einen Eingabestream mit mehreren Generatoren sukzessive aufzusplitten, also z.B. ein HTML-File -> Tabellen -> Zeilen -> Elemente)
lxml bietet da z.B. die iterparse()-Funktion. Im re-Modul gibt es finditer(). Evtl. durchforstest Du die Standard-Lib einfach mal nach solchen Funktionen? Meine Beispiele implizieren den Sinn dahinter ja schon ganz gut: Bei großen Datenmengen muss man eben nicht erst alles in den Speicher einlesen, sondern arbeitet eben die gewünschten Fragmente Stück für Stück ab.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
LanX
User
Beiträge: 92
Registriert: Samstag 20. Februar 2010, 12:46

Danke für die Links!
Hyperion hat geschrieben: Ist das denn wirklich das primäre Ziel von Generatoren? Ich war bisher davon ausgegangen, dass es primär um das Sparen von Speicher und Prozessorlast geht.
Schon klar mir geht's aber um das Idiom. Mit Statusvariablen im Closure kann ich eine Funktion auch bereits ohne "yield" zur Continuation machen, um dieses Ziel zu erreichen.

Es ist halt mehr Aufwand.

Ein eigenständiges Idiom hingegen rechtfertigt sich über Häufigkeit der Anwendungsfälle.
BlackJack

@LanX: Ob sich Code dadurch unbedingt vereinfacht weiss ich nicht -- er ist halt anders organisiert als ohne Generatoren. Ich sehe den Vorteil eher in der Skalierbarkeit. Wenn man Teilergebnisse "lazy" berechnet, ist der nötige Speicherplatz bei vielen Szenarien begrenzt.

Anstelle von Listen bieten sich Generatoren an, wenn die Liste nicht tatsächlich komplett benötigt wird und nur einmal elementweise verarbeitet wird. Da dann auch öfter mal als Generatorausdruck und nicht mit ``yield``.

Wenn man Problemlösungen so formuliert, dass es um Datenströme geht, und es um Datenquellen, Filter, Manipulatoren, und Datensenken geht, dann kommt ``yield`` in den ersten dreien ab und zu vor. Wenn man es nicht irgendwie "verstecken" kann, zum Beispiel durch Funktionen aus dem `itertools`-Modul. Für deren Implementierung man meistens auch wieder ``yield`` braucht.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

LanX hat geschrieben:Kann mir da jemand schicke Anwendungsfälle zeigen, wo sich Code dank Generatoren vereinfacht?
Vereinfacht eigentlich jede Funktion über deren Ergebnis man iterieren möchte.

Code: Alles auswählen

def get_files(directory):
    for file in do_something(directory):
        yield file

# vs.

def get_files(directory):
    files = []
    for file in do_something(directory):
        files.append(file)
    return files

# Benutzung

for file in get_files("."):
    print file
Außerdem spart das natürlich auch massiv Speicher. Die erste Funktion könnte man auch mit einer Klasse oder Closures hinkriegen, aber nicht in 3 gut lesbaren Zeiten.

Code: Alles auswählen

class get_files(object):
    def __init__(self, directory):
        self._iterator = iter(do_something(directory))
    def __iter__(self): return self    
    def next(self):
        return self._iterator.next()
Zuletzt geändert von Darii am Freitag 1. April 2011, 06:53, insgesamt 1-mal geändert.
problembär

Ich verzichte bewußt auf 'yield' usw., weil ich's nicht auf Anhieb verstehe, jedesmal nachgucken müßte und deshalb nur schwer lesbaren Code schreiben würde.

Sonst schreibt doch am besten gleich sowas (von hier):

Code: Alles auswählen

perl -e '@P=split//,".URRUU\c8R";@d=split//,"\nrekcah lreP rehtona tsuJ";sub p{@p{"r$p","u$p"}=(P,P);pipe"r$p","u$p";++$p;($q*=2)+=$f=!fork;map{$P=$P[$f^ord
($p{$_})&6];$p{$_}=/^$P/ix?$P:close$_}keys%p}p;p;p;p;p;map{$p{$_}=~/^[P.]/&& close$_}%p;wait until$?;map{/^r/&&<$_>}%p;$_=$d[$q];sleep rand(2)if/\S/;print'
Spart bestimmt 'ne Menge Speicher.

Gruß
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@problembär
Dann ist es ja nicht mehr weit bis zu Brainfuck und du kannst nochmal gut 99% Speicher sparen.

Generatoren sind doch schlicht weg genial. Ich glaube ohne diese würde Python um unlängen langsamer und wesentlich weniger flexibel sein, vorallem bei großen Mengen an Daten. "yield" selbst benutz ich selber kaum, aber Generatorenausdrücke ziemlich häufig. Use Cases: Schlicht weg überall da wo man die Skalierbarkeit benötigt.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@problembär: Die Verwendung von ``yield`` ist doch nun wirklich kinderleicht. Es ist im Prinzip wie ein ``return``, nur, dass du danach nicht die Funktion verlässt, sondern sie angehalten wird, sich den lokalen Zustand merkt und, beim nächsten Iterationsschritt, bis zum nächsten ``yield`` weiterläuft. Der dadurch erzeugte Generator liefert dann keine Werte mehr, wenn du ``StopIteration`` wirfst oder das Ende der Funktion erreicht ist.

Wen du Listen explizit mit ``lst.append`` aufbaust, machst du fast das gleiche. Du ersetzt einfach die ``lst.append`` durch ``yield`` und den Rückgabewert der Funktion lässt du weg.
Das Leben ist wie ein Tennisball.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

`yield` ist dann praktisch, wenn ein Generator-Ausdruck dreifache Verschachtelung oder so benötigen würde. Es ist in komplexeren Anwendungsfällen, wo in jedem Durchlauf eine gewisse Menge an Code zur Manipulation anfällt, halt lesbarer, sofern man es denn einmal verstanden hat.
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Bei unendlichen Generatoren würde ich auch eher ein while-True-yield-break gegenüber einem Generatorausdruck mittels itertools.repeat(True) bevorzugen.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hm, "Verschachtelung" könnte irreführend von mir ausgedrückt worden sein. Was ich meinte, war überspitzt gesagt die Vermeidung von Code wie diesem:

Code: Alles auswählen

results = (SuperParser(make_spam(to_bar(foo(get_string(x), get_number(x))))).raw for x in get_data_from_file(f))
Hier würde ich eine eigene Funktion bevorzugen, die für jeden Durchlauf die Ergebnisse der Zwischenschritte an diverse Namen bindet und das Ergebnis jeweils mittels `yield` ausgibt.
LanX
User
Beiträge: 92
Registriert: Samstag 20. Februar 2010, 12:46

problembär hat geschrieben:Sonst schreibt doch am besten gleich sowas (von hier):

Code: Alles auswählen

perl -e '@P=split//,".URRUU\c8R";@d=split//,"\nrekcah lreP rehtona tsuJ";sub p{@p{"r$p","u$p"}=(P,P);pipe"r$p","u$p";++$p;($q*=2)+=$f=!fork;map{$P=$P[$f^ord
($p{$_})&6];$p{$_}=/^$P/ix?$P:close$_}keys%p}p;p;p;p;p;map{$p{$_}=~/^[P.]/&& close$_}%p;wait until$?;map{/^r/&&<$_>}%p;$_=$d[$q];sleep rand(2)if/\S/;print'
selbst wem yield zu schwer ist, dem bleibt noch der Trost nach "perl golfing" zu googlen.

Aber wir lernen "FUD" steckt in "Fundi" ...
LanX
User
Beiträge: 92
Registriert: Samstag 20. Februar 2010, 12:46

snafu hat geschrieben:`yield` ist dann praktisch, wenn ein Generator-Ausdruck dreifache Verschachtelung oder so benötigen würde. Es ist in komplexeren Anwendungsfällen, wo in jedem Durchlauf eine gewisse Menge an Code zur Manipulation anfällt, halt lesbarer, sofern man es denn einmal verstanden hat.
Einverstanden, aber ich wär für ein nettes Beispiel dankbar, das ich nem Haufen Sysadmins verkaufen kann. 8)
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Wenn du mit streams arbeitest oder sehr großen Dateien die nicht in den Speicher passen musst du mit Iteratoren (und Generatoren) arbeiten. Genau dann macht yield Sinn.
LanX
User
Beiträge: 92
Registriert: Samstag 20. Februar 2010, 12:46

DasIch hat geschrieben:Wenn du mit streams arbeitest oder sehr großen Dateien die nicht in den Speicher passen musst du mit Iteratoren (und Generatoren) arbeiten. Genau dann macht yield Sinn.
Klar für Streams braucht man Iteratoren, aber werden Iteratoren in Python zwangsläufig mit yield konstruiert???

Whatever, das PEP war sehr aufschlussreich, ich seh jetzt klarer!

Danke!
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

LanX hat geschrieben:Klar für Streams braucht man Iteratoren, aber werden Iteratoren in Python zwangsläufig mit yield konstruiert???
Hättest du mein Post gelesen wüsstest du die Antwort.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

LanX hat geschrieben:Klar für Streams braucht man Iteratoren, aber werden Iteratoren in Python zwangsläufig mit yield konstruiert???
Darii hat doch gezeigt wie das ohne ``yield`` gehen würde…
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

LanX hat geschrieben:Klar für Streams braucht man Iteratoren, aber werden Iteratoren in Python zwangsläufig mit yield konstruiert???
Jein. Du kannst auch eine Klasse schreiben die __iter__ und next implementiert und den State in Attributen speichert, ist aber wesentlich mehr Code und bei weitem nicht so elegant insbesondere dann wenn du auch noch send und throw haben willst.
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

DasIch: Man kann auch einen Generatorausdruck schreiben.
LanX
User
Beiträge: 92
Registriert: Samstag 20. Februar 2010, 12:46

Darii hat geschrieben:
LanX hat geschrieben:Klar für Streams braucht man Iteratoren, aber werden Iteratoren in Python zwangsläufig mit yield konstruiert???
Hättest du mein Post gelesen wüsstest du die Antwort.
Ich habe deinen Post gelesen und das Prinzip der rhetorischen Frage ist dir bekannt?

(Vorsicht: die letzte Frage ist rekursiv ;)
Antworten