Frage

Wie würde man in Python eine iterative Funktion (oder ein Iteratorobjekt) erstellen?

War es hilfreich?

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:

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)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top