Wie würde man eine iterative Funktion (oder ein Iteratorobjekt) in Python erstellen?
Iteratorobjekte in Python entsprechen dem Iteratorprotokoll, was im Grunde bedeutet, dass sie zwei Methoden bereitstellen: __iter__()
und __next__()
.
Das __iter__
gibt das Iteratorobjekt zurück und wird implizit zu Beginn von Schleifen aufgerufen.
Die __next__()
Methode gibt den nächsten Wert zurück und wird bei jedem Schleifeninkrement implizit aufgerufen. Diese Methode löst eine StopIteration-Ausnahme aus, wenn kein Wert mehr zurückgegeben werden muss, der implizit von Schleifenkonstrukten erfasst wird, um die Iteration zu stoppen.
Hier ist ein einfaches Beispiel für einen Zähler:
class Counter:
def __init__(self, low, high):
self.current = low - 1
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
self.current += 1
if self.current < self.high:
return self.current
raise StopIteration
for c in Counter(3, 9):
print(c)
Dies wird gedruckt:
3
4
5
6
7
8
Dies ist einfacher mit einem Generator zu schreiben, wie in einer vorherigen Antwort beschrieben:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
Die gedruckte Ausgabe ist dieselbe. Unter der Haube unterstützt das Generatorobjekt das Iteratorprotokoll und macht etwas Ähnliches wie die Klasse Counter.
Der Artikel von David Mertz, Iteratoren und einfache Generatoren , ist eine ziemlich gute Einführung.
__next__
.counter
ist ein Iterator, aber es ist keine Sequenz. Es speichert seine Werte nicht. Sie sollten den Zähler beispielsweise nicht in einer doppelt verschachtelten for-Schleife verwenden.__iter__
(zusätzlich zu in__init__
) zugewiesen werden . Andernfalls kann das Objekt nur einmal iteriert werden. Wenn Sie beispielsweise sagenctr = Counters(3, 8)
, können Sie nichtfor c in ctr
mehr als einmal verwenden.Counter
ist ein Iterator, und Iteratoren sollten nur einmal iteriert werden. Wenn Sie Zurücksetzenself.current
in__iter__
, dann eine verschachtelte Schleife über dieCounter
vollständig gebrochen werden würde, und alle Arten von angenommenen Verhaltensweisen von Iteratoren (das Aufrufiter
an ihnen ist idempotent) verletzt werden . Wenn Siectr
mehr als einmal iterieren möchten , muss es sich um einen nicht iterierbaren Iterator handeln, bei dem bei jedem__iter__
Aufruf ein brandneuer Iterator zurückgegeben wird. Der Versuch zu mischen und abzugleichen (ein Iterator, der beim Aufrufen implizit zurückgesetzt__iter__
wird) verstößt gegen die Protokolle.Counter
ein Nicht-Iterator iterierbar sein möchten, würden Sie die Definition von__next__
/next
vollständig entfernen und wahrscheinlich__iter__
als Generatorfunktion derselben Form wie der am Ende dieser Antwort beschriebene Generator neu definieren (außer anstelle der Grenzen) Ausgehend von Argumenten bis__iter__
wären dies Argumente, auf die__init__
gespeichert werden sollself
und auf die vonself
in zugegriffen wird__iter__
.Es gibt vier Möglichkeiten, eine iterative Funktion zu erstellen:
__iter__
und__next__
(odernext
in Python 2.x))__getitem__
).Beispiele:
So sehen Sie alle vier Methoden in Aktion:
Was in ... resultiert:
Hinweis :
Die beiden Generatortypen (
uc_gen
unduc_genexp
) können nicht seinreversed()
; Der einfache Iterator (uc_iter
) würde die__reversed__
magische Methode benötigen (die laut den Dokumenten einen neuen Iterator zurückgeben muss, aber die Rückgabeself
funktioniert (zumindest in CPython)). und das getitem iteratable (uc_getitem
) muss die__len__
magische Methode haben:Um die sekundäre Frage von Colonel Panic zu einem unendlich träge bewerteten Iterator zu beantworten, sind hier diese Beispiele, die jede der vier oben genannten Methoden verwenden:
Was dazu führt (zumindest für meinen Probelauf):
Wie wähle ich das zu verwendende aus? Dies ist meist Geschmackssache. Die beiden Methoden, die ich am häufigsten sehe, sind Generatoren und das Iteratorprotokoll sowie ein Hybrid (
__iter__
Rückgabe eines Generators).Generatorausdrücke sind nützlich, um Listenverständnisse zu ersetzen (sie sind faul und können so Ressourcen sparen).
Wenn Sie Kompatibilität mit früheren Python 2.x-Versionen benötigen, verwenden Sie
__getitem__
.quelle
uc_iter
sollte ablaufen, wenn sie fertig ist (andernfalls wäre sie unendlich). Wenn Sie es erneut tun möchten, müssen Sie einen neuen Iterator erhalten, indem Sieuc_iter()
erneut aufrufen .self.index = 0
in__iter__
so dass Sie oft über laufen kann. Sonst kannst du nicht.Zuallererst ist das itertools-Modul unglaublich nützlich für alle Arten von Fällen, in denen ein Iterator nützlich wäre, aber hier ist alles, was Sie benötigen, um einen Iterator in Python zu erstellen:
Ist das nicht cool? Die Ausbeute kann verwendet werden, um eine normale Rendite in einer Funktion zu ersetzen . Das Objekt wird trotzdem zurückgegeben, aber anstatt den Status zu zerstören und zu beenden, wird der Status gespeichert, wenn Sie die nächste Iteration ausführen möchten. Hier ist ein Beispiel dafür in Aktion, das direkt aus der Funktionsliste von itertools entnommen wurde :
Wie in der Funktionsbeschreibung angegeben (es ist die Funktion count () aus dem itertools-Modul ...), wird ein Iterator erzeugt, der aufeinanderfolgende Ganzzahlen zurückgibt, die mit n beginnen.
Generatorausdrücke sind eine ganz andere Dose Würmer (fantastische Würmer!). Sie können anstelle eines Listenverständnisses verwendet werden , um Speicherplatz zu sparen (Listenverständnisse erstellen eine Liste im Speicher, die nach der Verwendung zerstört wird, wenn sie keiner Variablen zugewiesen sind, aber Generatorausdrücke können ein Generatorobjekt erstellen ..., was eine ausgefallene Methode ist Iterator sagen). Hier ist ein Beispiel für eine Definition eines Generatorausdrucks:
Dies ist unserer obigen Iteratordefinition sehr ähnlich, außer dass der gesamte Bereich vorgegeben ist, um zwischen 0 und 10 zu liegen.
Ich habe gerade xrange () gefunden (überrascht, dass ich es vorher noch nicht gesehen hatte ...) und es dem obigen Beispiel hinzugefügt. xrange () ist eine iterierbare Version von range () die den Vorteil hat, dass die Liste nicht vorab erstellt wird. Es wäre sehr nützlich, wenn Sie einen riesigen Datenbestand hätten, über den Sie iterieren könnten, und nur so viel Speicher hätten, um dies zu tun.
quelle
Ich sehe einige von euch tun
return self
in__iter__
. Ich wollte nur darauf hinweisen, dass__iter__
selbst ein Generator sein kann (wodurch die Notwendigkeit beseitigt__next__
undStopIteration
Ausnahmen ausgelöst werden ).Natürlich könnte man hier genauso gut direkt einen Generator bauen, aber für komplexere Klassen kann es nützlich sein.
quelle
return self
in__iter__
. Als ich es versuchen wollte,yield
fand ich, dass Ihr Code genau das tat, was ich versuchen wollte.next()
?return iter(self).next()
?self.current
oder einen anderen Zähler im Auge behalten müssen . Dies sollte die am besten gewählte Antwort sein!iter
Instanzen der Klasse aufrufen , aber sie sind selbst keine Instanzen der Klasse.Diese Frage bezieht sich auf iterierbare Objekte, nicht auf Iteratoren. In Python sind Sequenzen auch iterierbar. Eine Möglichkeit, eine iterierbare Klasse zu erstellen, besteht darin, sie wie eine Sequenz zu verhalten, dh sie
__getitem__
und__len__
Methoden anzugeben. Ich habe dies auf Python 2 und 3 getestet.quelle
__len__()
Methode haben.__getitem__
allein mit dem erwarteten Verhalten ist ausreichend.Alle Antworten auf dieser Seite eignen sich hervorragend für ein komplexes Objekt. Aber für jene , die builtin iterable Typen als Attribut, wie
str
,list
,set
oderdict
, oder jede Implementierungcollections.Iterable
können Sie bestimmte Dinge in der Klasse weglassen.Es kann verwendet werden wie:
quelle
return iter(self.string)
.Dies ist eine iterierbare Funktion ohne
yield
. Es nutzt dieiter
Funktion und einen Abschluss, der seinen Status in einem veränderlichen (list
) im umschließenden Bereich für Python 2 hält.Bei Python 3 wird der Abschlussstatus im umschließenden Bereich unveränderlich gehalten und
nonlocal
im lokalen Bereich zum Aktualisieren der Statusvariablen verwendet.Prüfung;
quelle
iter
, aber um ganz klar zu sein: Dies ist komplexer und weniger effizient als nur die Verwendung eineryield
basierten Generatorfunktion; Python bietet eine Menge Interpreter-Unterstützung füryield
basierte Generatorfunktionen, die Sie hier nicht nutzen können, wodurch dieser Code erheblich langsamer wird. Trotzdem hochgestimmt.Wenn Sie nach etwas Kurzem und Einfachem suchen, reicht es Ihnen vielleicht:
Anwendungsbeispiel:
quelle
Inspiriert von Matt Gregorys Antwort ist hier ein etwas komplizierterer Iterator, der a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz zurückgibt
quelle