Sehen wir uns die Sprachspezifikationen für Python-Iteratoren und -Generatoren an

(Ich habe versucht, mich spät im Adventskalender anzumelden, aber es war bereits voll, also habe ich es als regulären Artikel veröffentlicht.)

Einführung

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.

Grundlegende Dinge

It.next () in Python2, next (it) in Python3

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.)

Ein Objekt einer Klasse, die die Methode __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.

StopIteration-Ausnahme, wenn der Iterator endet

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.

Iterator und Generator sind unterschiedlich

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'

Wusstest du das?

Iterator im Iter-Format (aufrufbar, Sentinel)

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)

Wert an Generator generator.send senden

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.

Senden Sie eine Ausnahme an den Generator generator.throw

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)

Generator schließen schließen

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.

[3.3 oder höher] Delegierungssyntax an Subgenerator

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.

Zusammenfassung

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

Sehen wir uns die Sprachspezifikationen für Python-Iteratoren und -Generatoren an
Python Iterator und Generator
[Python] Ein grobes Verständnis von Iterablen, Iteratoren und Generatoren
Generieren Sie Fibonacci-Zahlen mit Python-Closures, Iteratoren und Generatoren
Python-Listeneinschlussnotation und Generator
Die Geschichte von Python und die Geschichte von NaN
[Python] Lass uns alle und jeden meistern
Überprüfung der Grundlagen von Python (FizzBuzz)
Ändern Sie die Sättigung und Helligkeit von Farbspezifikationen wie # ff000 in Python 2.5
[Blender x Python] Lass uns das Material beherrschen !!
Überprüfen Sie das Konzept und die Terminologie der Regression
Lesen wir die RINEX-Datei mit Python ①
Fassen wir den Python-Codierungsstandard PEP8 (1) zusammen.
Was ist mit 2017 rund um die Crystal-Sprache? (Täuschung)
Fassen wir den Python-Codierungsstandard PEP8 (2) zusammen.
Socket-Kommunikation in C-Sprache und Python
Academia Potter und der mysteriöse Python-Pass
Python open und io.open sind gleich
Ich verglich die Geschwindigkeit von Go Language Web Framework Echo und Python Web Framework Flask
Lassen Sie uns mit Python Receive spielen und den Text des Eingabeformulars speichern / anzeigen