(Ich habe versucht, mich spät im Adventskalender anzumelden, aber es war bereits voll, also habe ich es als regulären Artikel veröffentlicht.)
Es gab einige Dinge, von denen ich dachte, ich wüsste etwas über die Sprachspezifikationen rund um Python-Iteratoren und -Generatoren, und es gab einige Funktionen, die ich hinzugefügt hatte, aber nicht kannte. Deshalb habe ich sie hier zusammengefasst.
In diesem Artikel wird "es" als Variable behandelt, die auf einen Iterator verweist, und "Klass" wird als benutzerdefinierte Klasse behandelt, sofern nicht anders angegeben. Behandle x
als eine Variable, die auf ein Objekt zeigt.
Die Art und Weise, das nächste Element aus dem Iterator abzurufen, hat sich zwischen Python 2 und 3 geändert. In Python2 ist es "it.next ()" und in Python3 ist es "next (it)". Wenn Sie eine iteratorähnliche Klasse selbst implementieren möchten, können Sie "Klass.next" in Python2 und "Klass .__ next__" in Python3 implementieren. (In diesem Artikel verwenden wir das folgende Python3-Format.)
__iter__
implementiert, die einen Iterator zurückgibt, wird als iterable bezeichnet.Für iterierbare Objekte können Sie einen Iterator wie "iter (x)" erstellen. Es kann auch nach in in einer for-Anweisung oder auf der rechten Seite des in-Operators angegeben werden (der in-Operator versucht "contains", falls vorhanden, oder "iter", wenn dies nicht der Fall ist). Beispiele für iterable Elemente sind Listen-, Tupel-, Dikt-, Str- und Dateiobjekte. Der Iterator selbst ist ebenfalls iterierbar. Alternativ kann auch das Objekt der Klasse, die die Methode getitem implementiert, iteriert werden. Ein Iterator "iter (x)", der aus einem Objekt einer Klasse erstellt wurde, in der "iter" nicht implementiert ist und "getitem" implementiert ist, lautet bei jedem nächsten Aufruf "x [0]", "x [1]". Es gibt ", ..." zurück und löst eine "StopIteration" -Ausnahme aus, wenn ein "IndexError" ausgelöst wird.
Fahren Sie mit "next (it)" fort, und schließlich wird eine "StopIteration" -Ausnahme ausgelöst, wenn das nächste Element nicht mehr vorhanden ist. Wenn Sie "Klass .__ next__" implementieren, lösen Sie eine "StopIteration" -Ausnahme aus, wenn nichts mehr zurückgegeben werden muss.
Ein "Generator" ist eine Funktion, die einen Iterator zurückgibt, ähnlich einer regulären Funktion, jedoch mit einer Yield-Anweisung. Der Generator selbst ist kein Iterator. Ein "Generatorausdruck" ist auch ein Ausdruck, der einen Iterator zurückgibt, ähnlich der Listeneinschlussnotation, jedoch in Kreisen anstelle von Quadraten eingeschlossen ist.
iter(it) is it
Wenn "es" ein Iterator ist, sollte "iter (es)" es "selbst zurückgeben. Das heißt, wenn Sie einen Iterator implementieren, sollten Sie etwas wie "Klass .__ iter __ (self): return self" sagen.
In der for-Anweisung wird erwartet, dass "for x in it:" und "for x in iter (it):" äquivalent sind.
Das Folgende ist ein Beispiel dafür, was passiert, wenn it
und iter (it)
unterschiedlich sind.
es und iter(it)
print(sys.version) # ==> 3.4.1 (default, May 23 2014, 17:48:28) [GCC]
# iter(it)Wenn es zurückgibt
class Klass:
def __init__(self):
self.x = iter('abc')
def __iter__(self):
return self
def __next__(self):
return next(self.x)
it = Klass()
for x in it:
print(x) # ==> 'a', 'b', 'c'
# iter(it)Gibt es nicht zurück
class Klass2(Klass):
def __iter__(self):
return iter('XYZ')
it = Klass2()
for x in it:
print(x) # ==> 'X', 'Y', 'Z'
print(next(it)) # ==> 'a'
Wenn iter mit zwei Argumenten aufgerufen wird, wird immer noch ein Iterator zurückgegeben, aber das Verhalten ist sehr unterschiedlich. Wenn zwei Argumente vorhanden sind, muss das erste Argument ein aufrufbares Objekt (eine Funktion oder ein anderes Objekt mit call- Methoden) sein, das nicht iterierbar ist. Der von diesem Aufruf zurückgegebene Iterator kann bei jedem nächsten Aufruf ohne Argumente aufgerufen werden. Löst eine StopIteration-Ausnahme aus, wenn das zurückgegebene Ergebnis gleich Sentinel ist. Wenn Sie es wie einen Generator mit Pseudocode schreiben, verhält es sich so.
2 Argument iter
def iter_(callable, sentinel):
while 1:
a = callable()
if a == sentinel:
raise StopIteration
else:
yield a
In Pythons Offizielle Dokumentation heißt es, dass dies beispielsweise beim Lesen einer Datei nützlich ist, bis eine leere Zeile angezeigt wird.
Zitiert aus dem offiziellen Dokument
with open('mydata.txt') as fp:
for line in iter(fp.readline, ''):
process_line(line)
Im Generator
v = (yield x)
Wenn Sie wie schreiben, können Sie den Wert auf v setzen, wenn der Generator neu startet.
Wenn der Generator als nächstes normal neu gestartet wird, ist v Keine.
Wenn Sie die send-Methode anstelle von next wie "gen.send (a)" aufrufen, wird der Generator neu gestartet und v enthält a. Es gibt dann zurück, wenn der Wert wie beim nächsten Aufruf ausgegeben wird, und löst eine StopIteration-Ausnahme aus, wenn nichts ausgegeben wird.
Offizielles Dokument enthält ein Beispiel für einen Zähler mit einer Wertänderungsfunktion.
Zitiert aus dem offiziellen Dokument
def counter(maximum):
i = 0
while i < maximum:
val = (yield i)
# If value provided, change counter
if val is not None:
i = val
else:
i += 1
Apropos. Sie können send nicht plötzlich verwenden, und Sie müssen mindestens einmal das nächste Mal ausführen, bevor Sie send verwenden können. (TypeError: Nicht-None-Wert kann nicht an einen gerade gestarteten Generator gesendet werden
.) Wie Sie sehen können, wird der Wert gesendet, wenn er noch nie der nächste war. Wahrscheinlich, weil es keinen Platz gibt.
generator.throw(type[, value[, traceback]])
Ermöglicht das Auslösen einer Ausnahme, bei der der Generator unterbrochen wurde.
Wenn der Generator einen Wert liefert, gibt er diesen zurück und löst eine StopIteration-Ausnahme aus, wenn nichts ergibt. Die ausgelöste Ausnahme wird unverändert weitergegeben, wenn sie nicht verarbeitet wird. (Ehrlich gesagt kann ich mir keine effektive Nutzung vorstellen)
Löst eine GeneratorExit-Ausnahme aus, bei der der Generator unterbrochen wurde. Wenn eine GeneratorExit- oder StopIteration-Ausnahme ausgelöst wird, endet generator.close () dort. Wenn ein Wert zurückgegeben wird, wird ein RuntimeError ausgelöst. Wenn der Generator ursprünglich geschlossen war, tun Sie nichts. In Pseudocode geschrieben, sieht es so aus?
generator.Enge Verarbeitung
def generator_close(gen):
try:
gen.throw(GeneratorExit)
except (GeneratorExit, StopIteration):
return
throw RuntimeError
Ich kann mir auch keine Verwendung dafür vorstellen. Es gibt keine Garantie dafür, dass es aufgerufen wird, daher können Sie nichts schreiben, was aufgerufen werden soll. Außerdem konnte ich kein Dokument finden, das dies eindeutig angibt, aber es scheint, dass eine GeneratorExit-Ausnahme an den Generator ausgelöst wird, wenn mit der for-Anweisung gebrochen wird.
Eine Generator-Exit-Ausnahme tritt auf, wenn mit einer for-Anweisung gebrochen wird
def gen():
for i in range(10):
try:
yield i
except GeneratorExit:
print("Generator closed.")
raise
for i in gen():
break # ==> "Generator closed." is printed.
Sie können expr sequentiell zurückgeben, indem Sie "yield from expr" (expr ist ein Ausdruck, der eine Iterierbarkeit zurückgibt) in den Generator schreiben. Ohne Berücksichtigung des Sendens sind die folgenden zwei Codes äquivalent.
Delegierung an Subgenerator
def gen1():
yield from range(10)
yield from range(20)
def gen2():
for i in range(10):
yield i
for i in range(20):
yield i
Wenn send gesendet wird, wird der gesendete Wert an den Subgenerator übergeben.
Iteratoren werden oft in Python verwendet, aber sie hörten oft bei dem auf, was sie über ihre Spezifikationen und ihr altes Wissen zu wissen glaubten. Daher habe ich die Sprachspezifikationen anhand der offiziellen Dokumentation überprüft. Es gab einige Dinge, die ich nicht wusste, aber um ehrlich zu sein, die meisten bieten keine nützlichen Verwendungsmöglichkeiten. Wenn es so etwas wie "Es gibt andere Spezifikationen wie diese" oder "Ich verwende diese Funktion so" gibt, schreiben Sie es bitte in den Kommentarbereich.
Recommended Posts