Antworten:
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 sagen ctr = Counters(3, 8)
, können Sie nicht for c in ctr
mehr als einmal verwenden.
Counter
ist ein Iterator, und Iteratoren sollten nur einmal iteriert werden. Wenn Sie Zurücksetzen self.current
in __iter__
, dann eine verschachtelte Schleife über die Counter
vollständig gebrochen werden würde, und alle Arten von angenommenen Verhaltensweisen von Iteratoren (das Aufruf iter
an ihnen ist idempotent) verletzt werden . Wenn Sie ctr
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 soll self
und auf die von self
in zugegriffen wird __iter__
.
Es gibt vier Möglichkeiten, eine iterative Funktion zu erstellen:
__iter__
und__next__
(oder next
in Python 2.x))__getitem__
).Beispiele:
# generator
def uc_gen(text):
for char in text.upper():
yield char
# generator expression
def uc_genexp(text):
return (char for char in text.upper())
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text.upper()
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text.upper()
def __getitem__(self, index):
return self.text[index]
So sehen Sie alle vier Methoden in Aktion:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
print()
Was in ... resultiert:
A B C D E
A B C D E
A B C D E
A B C D E
Hinweis :
Die beiden Generatortypen ( uc_gen
und uc_genexp
) können nicht sein reversed()
; 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ückgabe self
funktioniert (zumindest in CPython)). und das getitem iteratable ( uc_getitem
) muss die __len__
magische Methode haben:
# for uc_iter we add __reversed__ and update __next__
def __reversed__(self):
self.index = -1
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += -1 if self.index < 0 else +1
return result
# for uc_getitem
def __len__(self)
return len(self.text)
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:
# generator
def even_gen():
result = 0
while True:
yield result
result += 2
# generator expression
def even_genexp():
return (num for num in even_gen()) # or even_iter or even_getitem
# not much value under these circumstances
# iterator protocol
class even_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
# getitem method
class even_getitem():
def __getitem__(self, index):
return index * 2
import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
limit = random.randint(15, 30)
count = 0
for even in iterator():
print even,
count += 1
if count >= limit:
break
print
Was dazu führt (zumindest für meinen Probelauf):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
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__
.
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 Sie uc_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:
Ausbeute
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 :
def count(n=0):
while True:
yield n
n += 1
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:
gen = (n for n in xrange(0,11))
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.
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__
und StopIteration
Ausnahmen ausgelöst werden ).
class range:
def __init__(self,a,b):
self.a = a
self.b = b
def __iter__(self):
i = self.a
while i < self.b:
yield i
i+=1
Natürlich könnte man hier genauso gut direkt einen Generator bauen, aber für komplexere Klassen kann es nützlich sein.
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.
class CustomRange:
def __init__(self, low, high):
self.low = low
self.high = high
def __getitem__(self, item):
if item >= len(self):
raise IndexError("CustomRange index out of range")
return self.low + item
def __len__(self):
return self.high - self.low
cr = CustomRange(0, 10)
for i in cr:
print(i)
__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
oder dict
, oder jede Implementierung collections.Iterable
können Sie bestimmte Dinge in der Klasse weglassen.
class Test(object):
def __init__(self, string):
self.string = string
def __iter__(self):
# since your string is already iterable
return (ch for ch in self.string)
# or simply
return self.string.__iter__()
# also
return iter(self.string)
Es kann verwendet werden wie:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
return iter(self.string)
.
Dies ist eine iterierbare Funktion ohne yield
. Es nutzt die iter
Funktion und einen Abschluss, der seinen Status in einem veränderlichen ( list
) im umschließenden Bereich für Python 2 hält.
def count(low, high):
counter = [0]
def tmp():
val = low + counter[0]
if val < high:
counter[0] += 1
return val
return None
return iter(tmp, None)
Bei Python 3 wird der Abschlussstatus im umschließenden Bereich unveränderlich gehalten und nonlocal
im lokalen Bereich zum Aktualisieren der Statusvariablen verwendet.
def count(low, high):
counter = 0
def tmp():
nonlocal counter
val = low + counter
if val < high:
counter += 1
return val
return None
return iter(tmp, None)
Prüfung;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
iter
, aber um ganz klar zu sein: Dies ist komplexer und weniger effizient als nur die Verwendung einer yield
basierten Generatorfunktion; Python bietet eine Menge Interpreter-Unterstützung für yield
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:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
Anwendungsbeispiel:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
Inspiriert von Matt Gregorys Antwort ist hier ein etwas komplizierterer Iterator, der a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz zurückgibt
class AlphaCounter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
alpha = ' abcdefghijklmnopqrstuvwxyz'
n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
if n_current > n_high:
raise StopIteration
else:
increment = True
ret = ''
for x in self.current[::-1]:
if 'z' == x:
if increment:
ret += 'a'
else:
ret += 'z'
else:
if increment:
ret += alpha[alpha.find(x)+1]
increment = False
else:
ret += x
if increment:
ret += 'a'
tmp = self.current
self.current = ret[::-1]
return tmp
for c in AlphaCounter('a', 'zzz'):
print(c)