Seite 1 von 2

Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 13:43
von LanX
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

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 14:04
von Hyperion
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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 14:25
von LanX
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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 14:31
von 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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 18:51
von Darii
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()

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 19:40
von 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ß

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 20:05
von Xynon1
@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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 20:52
von EyDu
@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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 21:03
von snafu
`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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 21:20
von derdon
Bei unendlichen Generatoren würde ich auch eher ein while-True-yield-break gegenüber einem Generatorausdruck mittels itertools.repeat(True) bevorzugen.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 21:36
von snafu
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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 23:23
von LanX
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" ...

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 23:38
von LanX
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)

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Donnerstag 31. März 2011, 23:55
von DasIch
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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Freitag 1. April 2011, 02:24
von LanX
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!

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Freitag 1. April 2011, 06:53
von Darii
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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Freitag 1. April 2011, 11:57
von Leonidas
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…

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Freitag 1. April 2011, 13:19
von DasIch
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.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Freitag 1. April 2011, 13:22
von derdon
DasIch: Man kann auch einen Generatorausdruck schreiben.

Re: Beispiele für Realworld Use Cases für Generatoren

Verfasst: Freitag 1. April 2011, 14:06
von LanX
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 ;)