wake-up-neo.com

__next__ in Generatoren und Iteratoren und was ist ein Methodenwrapper?

Ich las über Generator und Iteratoren und die Rolle von __next__().

'__next__' in dir(mygen). ist wahr

'__next__' in dir(mylist) ist falsch

Als ich genauer hinschaute,

'__next__' in dir (mylist.__iter__()) ist wahr

  1. warum ist__next__ nur verfügbar, um aufzulisten, aber nur für __iter__() und mygen, aber nicht für mylist. Wie ruft __iter__() den __next__ auf, wenn wir mit Listenverständnis durch die Liste gehen?

    Als ich versuchte, den Generator manuell hochzuschalten (+1), rief ich mygen.__next__() an. Es existiert nicht. Es existiert nur als mygen.__next__, der als Methoden-Wrapper bezeichnet wird.

  2. was ist ein Methoden-Wrapper und was macht er? Wie wird es hier angewendet: in mygen() and __iter__() ?

  3. wenn __next__ das ist, was sowohl der Generator als auch der Iterator bieten (und ihre einzigen Eigenschaften), was ist dann der Unterschied zwischen Generator und Iterator? *

    Antwort zu 3: Gelöst, wie vom Mod/Editor vermerkt:

    Unterschied zwischen Pythons Generatoren und Iteratoren

UPDATE: Sowohl Generator als auch Iterator haben __next__(). Mein Fehler. Beim Betrachten der Protokolle hat mir der Test mygen.__next__() irgendwie einen Stopiter-Ausnahmefehler beschert. Aber ich konnte diesen Fehler nicht noch einmal wiederholen.

Vielen Dank an alle für die Antwort!

6
theMobDog

Die speziellen Methoden __iter__ und __next__ sind Teil des Iteratorprotokolls zum Erstellen von Iteratortypen . Zu diesem Zweck müssen Sie zwei verschiedene Dinge unterscheiden: Iterables und Iteratoren .

Iterables sind Dinge, die iteriert werden können. Normalerweise handelt es sich um Containerelemente, die Elemente enthalten. Häufige Beispiele sind Listen, Tupel oder Wörterbücher.

Um eine Iteration zu iterieren, verwenden Sie einen Iterator. Ein Iterator ist das Objekt, das Ihnen beim Durchlaufen des Containers hilft. Wenn Sie zum Beispiel eine Liste durchlaufen, verfolgt der Iterator im Wesentlichen den aktuellen Index.

Um einen Iterator zu erhalten, wird die __iter__-Methode für die Iteration aufgerufen. Dies ist wie eine Factory-Methode, die einen neuen Iterator für diese bestimmte Iteration zurückgibt. Ein Typ, für den eine __iter__-Methode definiert ist, wandelt sie in eine Iteration um.

Der Iterator benötigt im Allgemeinen eine einzige Methode, __next__, die das Element next für die Iteration zurückgibt. Damit das Protokoll einfacher zu verwenden ist, sollte jeder Iterator auch ein Iterator sein, der sich selbst in der __iter__-Methode zurückgibt.

Als schnelles Beispiel wäre dies eine mögliche Iterator-Implementierung für eine Liste:

class ListIterator:
    def __init__ (self, lst):
        self.lst = lst
        self.idx = 0

    def __iter__ (self):
        return self

    def __next__ (self):
        try:
            item = self.lst[self.idx]
        except IndexError:
            raise StopIteration()
        self.idx += 1
        return item

Die Listenimplementierung könnte dann einfach ListIterator(self) aus der __iter__-Methode zurückgeben. Die eigentliche Implementierung der Listen erfolgt natürlich in C, das sieht also etwas anders aus. Aber die Idee ist die gleiche.

Iteratoren werden an verschiedenen Stellen in Python unsichtbar verwendet. Zum Beispiel eine for-Schleife:

for item in lst:
    print(item)

Dies ist ein bisschen dasselbe wie das Folgende:

lst_iterator = iter(lst) # this just calls `lst.__iter__()`
while True:
    try:
        item = next(lst_iterator) # lst_iterator.__next__()
    except StopIteration:
        break
    else:
        print(item)

Die for-Schleife fordert also einen Iterator vom iterierbaren Objekt an und ruft dann __next__ für dieses iterierbare Objekt auf, bis die StopIteration-Ausnahme gefunden wird. Dass dies unter der Oberfläche geschieht, ist auch der Grund, warum Iteratoren den __iter__ ebenfalls implementieren sollen: Andernfalls könnten Sie niemals einen Iterator überlaufen.


Bei Generatoren beziehen sich die Leute normalerweise auf einen Generator function, d. H. Eine Funktionsdefinition mit yield-Anweisungen. Wenn Sie diese Generatorfunktion aufrufen, erhalten Sie einen generator zurück. Ein Generator ist im Wesentlichen nur ein Iterator, wenn auch ein ausgefallener (da er sich mehr als durch einen Container bewegt). Als Iterator verfügt er über eine __next__-Methode zum "Generieren" des nächsten Elements und eine __iter__-Methode, um sich selbst zurückzugeben.


Ein Beispiel für eine Generatorfunktion wäre folgende:

def exampleGenerator():
    yield 1
    print('After 1')
    yield 2
    print('After 2')

Der Funktionsrumpf, der eine yield-Anweisung enthält, macht daraus eine Generatorfunktion. Das heißt, wenn Sie exampleGenerator() aufrufen, erhalten Sie ein generator - Objekt zurück. Generatorobjekte implementieren das Iteratorprotokoll, sodass wir __next__ darauf aufrufen können (oder die Funktion next() wie oben verwenden):

>>> x = exampleGenerator()
>>> next(x)
1
>>> next(x)
After 1
2
>>> next(x)
After 2
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    next(x)
StopIteration

Beachten Sie, dass beim ersten Aufruf von next() noch nichts gedruckt wurde. Das ist das Besondere an Generatoren: Sie sind faul und werten nur so viel aus, wie nötig ist, um das nächste Element aus dem Iterierbaren zu erhalten. Erst beim zweiten Aufruf von next() erhalten wir die erste gedruckte Zeile aus dem Funktionshauptteil. Und wir brauchen einen weiteren Aufruf von next(), um das Iterable zu erschöpfen (da sich kein anderer Wert ergibt).

Abgesehen von dieser Faulheit verhalten sich Generatoren jedoch nur als iterable. Sie erhalten am Ende sogar eine StopIteration-Ausnahme, mit der Generatoren (und Generatorfunktionen) als for-Schleifenquellen verwendet werden können und wo auch "normale" Iterable verwendet werden können.

Der große Vorteil von Generatoren und ihrer Faulheit ist die Fähigkeit, on demand zu erzeugen. Eine schöne Analogie dazu ist das endlose Scrollen auf Websites: Sie können Elemente nach und nach scrollen (Aufruf von next() am Generator), und ab und zu muss die Website ein Backend abfragen, um weitere Elemente abzurufen, durch die Sie blättern können . Im Idealfall geschieht dies, ohne dass Sie es merken. Und genau das macht ein Generator. Es erlaubt sogar Dinge wie diese:

def counter():
    x = 0
    while True:
        x += 1
        yield x

Nicht faul wäre dies unmöglich zu berechnen, da dies eine unendliche Schleife ist. Aber als Generator ist es möglich, dieses iterative Element nach einem Element zu konsumieren. Ursprünglich wollte ich Sie davon abhalten, diesen Generator als vollständig benutzerdefinierten Iteratortyp zu implementieren, aber in diesem Fall ist das eigentlich nicht allzu schwierig.

class CounterGenerator:
    def __init__ (self):
        self.x = 0

    def __iter__ (self):
        return self

    def __next__ (self):
        self.x += 1
        return self.x
20
poke

Warum ist __next__ nur für die Liste verfügbar, aber nur für __iter__() und mygen, nicht jedoch für mylist. Wie ruft __iter__()__next__ auf, wenn wir die Liste mit Listenverständnis durchgehen.

Da Listen über ein separates Objekt verfügen, das von iter zur Verarbeitung der Iteration zurückgegeben wird, wird dieses Objekt __iter__ fortlaufend aufgerufen. 

Also für Listen:

iter(l) is l # False, returns <list-iterator object at..>

Während für Generatoren:

iter(g) is g # True, its the same object

In Schleifenkonstrukten wird iter zuerst für das Zielobjekt aufgerufen, für das eine Schleife erstellt werden soll. iter ruft __iter__ auf und es wird erwartet, dass ein Iterator zurückgegeben wird; Sein __next__ wird aufgerufen, bis keine Elemente mehr verfügbar sind.

Was ist ein Methoden-Wrapper und was macht er? Wie wird es hier angewendet: in mygen() und __iter__()?

Ein Methodenwrapper ist, wenn ich mich nicht irre, eine in C implementierte Methode. Das ist, was beide iter(list).__iter__ (list ist ein in C implementiertes Objekt) und gen.__iter__ (nicht sicher hier, aber Generatoren sind wahrscheinlich auch).

Wenn __next__ sowohl der Generator als auch der Iterator (und ihre einzigen Eigenschaften) bereitstellen, was ist dann der Unterschied zwischen Generator und Iterator?

Ein Generator ist ein Iterator, ebenso wie der von iter(l) bereitgestellte Iterator. Es handelt sich um einen Iterator, da er eine __next__-Methode bereitstellt (die normalerweise in einer for-Schleife verwendet werden kann und Werte liefern kann, bis sie erschöpft ist).

__next__ und __iter__ sind Methodenwrapper für die Ausführung von next(some_gen) oder iter(some_sequence). next(some_gen) ist das gleiche wie some_gen.__next__()

Wenn ich also mygen = iter(mylist) mache, ist mygenmylist als Generatorobjekt implementiert und verfügt über einen __next__-Methodendeskriptor. Listen haben diese Methode nicht, da sie keine Generatoren sind.

Generatoren sind Iteratoren. Check out Unterschied zwischen Generatoren und Iteratoren

1
sytech