Erstellen Sie einen einfachen Python-Iterator
Frage
Wie würde man in Python eine iterative Funktion (oder ein Iteratorobjekt) erstellen?
Lösung
Iteratorobjekte in Python entsprechen dem Iteratorprotokoll, was im Wesentlichen bedeutet, dass sie zwei Methoden bereitstellen: __iter__()
Und next()
.Der __iter__
Gibt das Iteratorobjekt zurück und wird implizit am Anfang von Schleifen aufgerufen.Der next()
Die Methode gibt den nächsten Wert zurück und wird implizit bei jedem Schleifeninkrement aufgerufen. next()
Löst eine StopIteration-Ausnahme aus, wenn kein Wert mehr zurückgegeben werden kann, was implizit durch Schleifenkonstrukte 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
self.high = high
def __iter__(self):
return self
def next(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
for c in Counter(3, 8):
print c
Dadurch wird Folgendes gedruckt:
3
4
5
6
7
8
Dies lässt sich einfacher mit einem Generator schreiben, wie in einer früheren Antwort beschrieben:
def counter(low, high):
current = low
while current <= high:
yield current
current += 1
for c in counter(3, 8):
print c
Die gedruckte Ausgabe wird dieselbe sein.Unter der Haube unterstützt das Generatorobjekt das Iteratorprotokoll und macht etwas, das in etwa der Klasse Counter ähnelt.
Artikel von David Mertz, Iteratoren und einfache Generatoren, ist eine ziemlich gute Einführung.
Andere Tipps
Es gibt vier Möglichkeiten, eine iterative Funktion zu erstellen:
- Erstellen Sie einen Generator (verwendet die Schlüsselwort yield)
- Verwenden Sie einen Generatorausdruck (genexp)
- Erstellen Sie einen Iterator (definiert
__iter__
Und__next__
(odernext
in Python 2.x)) - Erstellen Sie eine Klasse, über die Python selbstständig iterieren kann (definiert
__getitem__
)
Beispiele:
# generator
def uc_gen(text):
for char in text:
yield char.upper()
# generator expression
def uc_genexp(text):
return (char.upper() for char in text)
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index].upper()
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text
def __getitem__(self, index):
result = self.text[index].upper()
return result
Um alle vier Methoden in Aktion zu sehen:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print ch,
print
Was dazu führt:
A B C D E
A B C D E
A B C D E
A B C D E
Notiz:
Die beiden Generatortypen (uc_gen
Und uc_genexp
) kann nicht sein reversed()
;der einfache Iterator (uc_iter
) würde das brauchen __reversed__
magische Methode (die einen neuen Iterator zurückgeben muss, der rückwärts geht);und das getitem iterable (uc_getitem
) muss das haben __len__
magische Methode:
# for uc_iter
def __reversed__(self):
return reversed(self.text)
# for uc_getitem
def __len__(self)
return len(self.text)
Um die sekundäre Frage von Colonel Panic zu einem unendlich träge ausgewerteten Iterator zu beantworten, finden Sie hier diese Beispiele, bei denen jede der vier oben genannten Methoden verwendet wird:
# 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 Beispiellauf):
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
Zuallererst die itertools-Modul ist unglaublich nützlich für alle möglichen Fälle, in denen ein Iterator nützlich wäre, aber hier ist alles, was Sie brauchen, um einen Iterator in Python zu erstellen:
Ertrag
Ist das nicht cool?Yield kann als Ersatz für eine normale verwendet werden zurückkehren in einer Funktion.Es gibt das Objekt trotzdem zurück, aber anstatt den Status zu zerstören und zu beenden, speichert es den Status für den Fall, dass Sie die nächste Iteration ausführen möchten.Hier ist ein Beispiel davon in Aktion, das direkt aus dem entnommen wurde itertools-Funktionsliste:
def count(n=0):
while True:
yield n
n += 1
Wie in der Funktionsbeschreibung angegeben (es ist die zählen() (Funktion aus dem Modul itertools...) erzeugt einen Iterator, der aufeinanderfolgende Ganzzahlen zurückgibt, beginnend mit n.
Generatorausdrücke sind eine ganz andere Dose Würmer (tolle Würmer!).Sie können anstelle von a verwendet werden Listenverständnis um Speicher zu sparen (Listenverständnisse erstellen eine Liste im Speicher, die nach der Verwendung zerstört wird, wenn sie keiner Variablen zugewiesen wird, aber Generatorausdrücke können ein Generatorobjekt erstellen ...was eine schicke Art ist, Iterator zu sagen).Hier ist ein Beispiel für eine Generatorausdrucksdefinition:
gen = (n for n in xrange(0,11))
Dies ist unserer Iteratordefinition oben sehr ähnlich, außer dass der gesamte Bereich auf einen Wert zwischen 0 und 10 festgelegt ist.
Ich habe gerade gefunden xrange() (überrascht, dass ich es noch nie gesehen hatte ...) und es dem obigen Beispiel hinzugefügt. xrange() ist eine iterierbare Version von Reichweite() Dies hat den Vorteil, dass die Liste nicht vorab erstellt werden muss.Es wäre sehr nützlich, wenn Sie einen riesigen Datenbestand zum Durchlaufen hätten und nur eine begrenzte Speicherkapazität dafür hätten.
Ich sehe einige von euch dabei return self
In __iter__
.Das wollte ich nur anmerken __iter__
selbst kann ein Generator sein (wodurch die Notwendigkeit entfällt). __next__
und erhöhen StopIteration
Ausnahmen)
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 erstellen, aber für komplexere Klassen kann es nützlich sein.
Bei dieser Frage geht es um iterierbare Objekte, nicht um Iteratoren.In Python sind auch Sequenzen iterierbar. Eine Möglichkeit, eine iterierbare Klasse zu erstellen, besteht also darin, sie wie eine Sequenz verhalten zu lassen, d. h.Gib es __getitem__
Und __len__
Methoden.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)
Dies ist eine iterierbare Funktion ohne yield
.Es nutzt die iter
Funktion und einen Abschluss, der seinen Zustand in einem veränderlichen Zustand hält (list
) im umschließenden Bereich für Python 2.
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 und unveränderlich gehalten nonlocal
wird im lokalen Bereich verwendet, um die Statusvariable zu aktualisieren.
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üfen;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
Alle Antworten auf dieser Seite sind für ein komplexes Objekt wirklich großartig.Aber für diejenigen, die integrierte iterierbare Typen als Attribute enthalten, z str
, list
, set
oder dict
, oder irgendeine Implementierung von collections.Iterable
, können Sie bestimmte Dinge in Ihrem Unterricht 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 string)
Es kann wie folgt verwendet werden:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
Wenn Sie etwas Kurzes und Einfaches suchen, reicht es vielleicht für Sie:
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 gibt es hier einen etwas komplizierteren 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)