Verständnisfrage zu Iteratoren und 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.
Antworten
Knax
User
Beiträge: 5
Registriert: Mittwoch 1. Oktober 2008, 20:10

Hallo habe nicht genau den Unterschied zwischen
Generatoren und iteratoren verstanden.
Bin Neueinsteiger in Python.

Meiner Meinung nach ist das eine oder andere doch überflüssig.

z.B.
kann ich einen Generator in einen Iterator packen,
aber was soll mir das denn bringen, dafür
ist doch eigentlich der Iterator gedacht.
Siehe folgendes Listing:

Code: Alles auswählen

class Fibonacci2(object): 
    def __init__(self, max_n): 
        self.MaxN = max_n 
 
    def __iter__(self): 
        n = 0 
        a, b = 0, 1 
        for n in xrange(self.MaxN): 
            a, b = b, a + b 
            yield a
Und die Behauptung ein Generator gibt einen Iterator zurück,
frage ich mich wofür habe ich denn dann den Generator ??
Wenn ich alles mit dem Iterator programmieren kann ?

umgekehrt wird ja gesagt das ein Generator einen
Iterator zurückgibt.

Dann wäre da noch die Sache mit den Iteratoren selbst.
Was genau bewirkt die Funktion __iter__(self)
bei einem Iterator ?

So wie ich verstanden habe ist diese Funktion dafür
zuständig das der Iterator mit einem definierten Zustand
anfängt zu iterieren, ähnlich wie die __init__(self) Funktion
bei den Klassen.
Aber wenn dies so wäre mit der __iter__(self) Funktion
dann frage ich mich warum ich mir die next() Funtktion
sparen kann bei folgendem Listing ?


Denn hier packe ich wieder einen Generator in die
Iterator-Funktion __iter__(self)

Code: Alles auswählen

def __iter__(self): 
        n = 0 
        a, b = 0, 1 
        for n in xrange(self.MaxN): 
            a, b = b, a + b 
            yield a
Irgendwie finde ich das Zusammenspiel verwirrend ?

Vielen Dank für eure Hilfe
BlackJack

@Knax: Eine Generatorfunktion ist ein einfacher Weg um einen Iterator zu erzeugen. Es gibt keine Iterator-Funktion, dein `__iter__()` ist eine *Generator*funktion, die einen Iterator liefert. Iteratoren sind Objekte mit einer `next()`-Methode, die entweder den nächsten Wert liefert, oder `StopIteration` auslöst, um das Ende zu signalisieren, und die sich selbst beim Aufruf von `__iter__()` zurückgeben. Und so ein Objekt bekommt man bei Funktionen mit einem ``yield`` quasi "geschenkt".

Ein Iterator, selbst implementiert, für Dein Beispiel würde so aussehen (ungetestet):

Code: Alles auswählen

class FibonacciIterator(object):
    def __init__(self, max_n):
        self.max_n = max_n
        self.n = 0
        self.a = 0
        self.b = 1
    
    def __iter__(self):
        return self
    
    def next(self):
        if self.n >= self.max_n:
            raise StopIteration()
        self.n += 1
        self.a, self.b = self.b, self.a + self.b
        return self.a


class Fibonacci(object): 
    def __init__(self, max_n):
        self.max_n = max_n
  
    def __iter__(self):
        return FibonacciIterator(self.max_n)
Knax
User
Beiträge: 5
Registriert: Mittwoch 1. Oktober 2008, 20:10

Hallo BlackJack, vielen Dank für
deine Antwort. Glaube ich habe es ungefähr verstanden.

Dann brauche ich eigentlich nur Generatoren
zu verwenden da diese ja sowieso Iteratoren erzeugen ?
Oder gibt es Fälle in denen es Sinn macht keine Generatoren
sondern Iteratoren zu verwenden ?
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Iteratoren sind Objekte, über die man iterieren kann (thanks, Captain Obvious!), sie also z. B. in einer `for`-Schleife durchlaufen. Dabei gibt es statische Iteratoren wie Tupel, Listen, Sets oder Dictionaries; statisch in dem Sinne, dass man sie "mehrmals benutzen" kann. Dagegen sind Generatoren quasi Wegwerf-Iteratoren, die man nur einmal verwenden kann.

Willst du also eine Sequenz von Objekten erzeugen und sie z. B. einmal filtern und anderswo per Index auf die ursprüngliche, ungefilterte Sequenz zugreifen, brauchst du eine beständige Sequenz. Ein Generator bietet sich an, wenn du die Ursprungssequenz nicht mehr benötigst und sie verworfen werden kann.

Der Vorteil von Generatoren besteht nun darin, dass sie nach und nach die Sequenz erzeugen und nicht wie etwa eine Liste einen Speicherbereich haben, den sie nach und nach füllen und der weiterhin fortbesteht. Bei sehr großen oder vielen Daten kann ein Generator also sehr viel weniger Speicherbedarf haben, da nie alle Daten parallel bereit gehalten werden.

So in etwa ;)
BlackJack

@Y0Gi: Tupel, Listen, Dictionaries, usw. sind keine Iteratoren sondern "iterables", d.h. Objekte bei denen man mit `iter()` einen Iterator bekommen kann.

Wenn's keine `next()`-Methode mit der entsprechenden Semantik hat, ist's kein Iterator.
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Ist ein Iterator ein eigener Typ oder ein Objekt mit `.next()`? Haben Letzteres nicht zwangsweise alle Objekte, die man in einem `for`-Loop benutzen kann? Oder verwechsle ich das gerade? Was ist mit `.__iter__()`?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Y0Gi hat geschrieben:Ist ein Iterator ein eigener Typ oder ein Objekt mit `.next()`?
Es ist in erster Linie ein Objekt mit einem bestimmten Verhalten, eben dass s ein ``.next()`` hat und entsprechende Exceptions wirft.
Y0Gi hat geschrieben:Haben Letzteres nicht zwangsweise alle Objekte, die man in einem `for`-Loop benutzen kann?
Nein, Tupeln oder Listen haben kein ``.next()``, aber das Objekt was rauskommt wenn man ``iter()`` auf einer Tupel anwendet hat ein ``.next()``. Genau das macht ``for`` auch.
Y0Gi hat geschrieben:Oder verwechsle ich das gerade? Was ist mit `.__iter__()`?
``__iter__()`` wird durch ``iter()`` aufgerufen und soll einen Iterator zurückgeben.
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

Code: Alles auswählen

In [1]: def foo():
   ...:     for i in xrange(10):
   ...:         yield i

In [2]: foo
Out[2]: <function foo at 0x11b69b0>

In [3]: foo()
Out[3]: <generator object foo at 0x11b2eb0>

In [4]: for element in dir(foo()):
   ...:     if element not in dir(foo):
   ...:         print element
   ...:
   ...:
__iter__
close
gi_code
gi_frame
gi_running
next
send
throw
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

@Knax

Der Vorteil liegt mitunter darin, dass du komplexere Iteratoren erzeugen kannst. Zum Beispiel könntest du ja die Iterator Klasse gesondert in die eigentliche Logik Klasse injizieren und in der __iter__ Methode dann davon eine Instanz zurückgeben lassen. Du bist einfach freier, musst aber Klassen nutzen. Die einzige Bedienung an einen Iterator ist, das die Daten, die er zum Iterieren anbietet, eben sequentiell über next() erreichbar sind.

Generatoren sind einfach vereinfachte Iteratoren, um sich mal eben einen Mittels einer Funktion zusammenbauen zu können, ohne dabei durch allerlei (langsamen) Boilerplate - Listen Code angewiesen zu sein. Sie sind schneller und du hast auch bessere Kontrolle über sie (Schließlich werfen sie bei falscher Nutzung irgendwann garantiert StopIteration).
Eben "Wegwerf" Iteratoren, wie Yogi sagte.
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Leonidas: Danke, alles klar. Da habe ich wohl vergessen, dass `for`-Schleifen (und LCs und GEs) implizit `iter()` auf das/die iterable anwenden.
Antworten