Die Python for-Anweisung ist eine sogenannte iterative Anweisung und wird häufig verwendet, um jedes Listenelement von Anfang an in der richtigen Reihenfolge zu extrahieren und zu verarbeiten.
Die Syntax lautet wie folgt.
for x in [1, 2, 3, 4, 5]:
# ...
Wenn Sie eine Liste nach dem Einfügen einfügen, können Sie mit jedem Element verarbeiten.
Abgesehen von der Liste werden häufig "Bereich (1, 6)" und "(1, 2, 3, 4, 5)" verwendet.
"[1, 2, 3, 4, 5]", "range (1, 6)" und "(1, 2, 3, 4, 5)" sind alle 1 in x, wenn sie auf das obige Syntaxbeispiel angewendet werden. , 2, 3, 4, 5 sind der Reihe nach zugeordnet.
Der Verarbeitungsablauf von "[1, 2, 3, 4, 5]" und "(1, 2, 3, 4, 5)" ist leicht vorstellbar.
Es scheint jedoch, dass "range (1, 6)" x zugewiesen ist, obwohl 2 bis 5 nicht geschrieben sind.
Warum.
Ich verstehe. Tatsächlich ist das, was von der Bereichsfunktion erstellt wird, keine Liste. Es ist auch kein Tupel. Es ist ein Entfernungsobjekt. Hmm ... das stimmt ... damit? ... wundert es sich nicht, warum Sie sich die Mühe machen, ein Bereichsobjekt zu erstellen? Interessiert es dich nicht, warum es eine Liste ist? Ist das so. Für Anfänger ist es intuitiver, eine Liste zu sein, daher ist dies nur eine Falle in der Überzeugung, dass "Benutzer sich nicht um den Inhalt der Black Box kümmern müssen". Lassen Sie uns hier unsere Intuition aktualisieren. Der Grund für das Erstellen eines Bereichsobjekts oder die allgemeine Idee der ** iterativen Verarbeitung ** macht das Programmieren Spaß, wenn Sie es beherrschen. Bitte beherrschen Sie es.
Bisher haben wir drei verschiedene Objekte nach dem in in der for-Anweisung angegeben.
Listen-, Tupel- und Bereichsobjekte.
Diese drei können mit einer for-Anweisung gedreht werden.
Aber was ist mit der Nummer 123456?
for i in 123456:
# ...
Leider bekomme ich eine Fehlermeldung. Was ist dieser Unterschied?
Betrachten Sie, ob der Typ, der den Python-Code analysiert, ein Objekt ist, das mit einer for-Anweisung gedreht werden kann?
Es ist nicht falsch, aber es ist nicht vollständig erklärt.
Dann entscheiden Sie, wo Sie in das Objekt schauen möchten, um es mit der for-Anweisung zu drehen.
Tatsächlich gibt es so etwas wie ** Beweis **, der einen Begleiter angibt, der in der for-Anweisung gedreht werden kann, und wenn Sie ihn in der for-Anweisung nach angeben, prüfen Sie, ob es diesen Beweis gibt. Solche Begleiter werden hier als ** iterierbar ** bezeichnet.
für Bunman "Proof Arne! OK!" list-chan "Danke!" Tupel-Chan "Danke!" range-chan "Danke!"
Was ist dann der Beweis?
Wenden Sie bei Verwendung der for-Anweisung die ** iter-Funktion ** (später beschrieben) intern auf die nach iter angegebene ** iterable ** an und konvertieren Sie sie in den ** iterator ** (später beschrieben). Wenden Sie dann die nächste Funktion ** so oft wie möglich ** auf diesen Itator an (siehe unten). Dies ist die interne Situation der for-Anweisung. (Es gibt ein anderes Muster innerer Umstände, aber ich werde es weglassen.)
??????
Wenn Sie nicht verstehen, machen Sie sich bitte Notizen und lesen Sie weiter.
Ein Iterator ist ein Objekt, das sich selbst zurückgibt, wenn die Iter-Funktion angewendet wird, und einen Wert, wenn die nächste Funktion angewendet wird.
Die iter-Funktion ruft intern die Methode iter des Objekts auf.
Die nächste Funktion ruft intern die Methode next des Objekts auf.
class MyIter:
def __iter__(self):
return self
def __next__(self):
return 1
my_iter = MyIter()
# iter(my_iter) == my_iter.__iter__() == my_iter
# next(my_iter) == my_iter.__next__()
Dies allein ist bereits ein Iterator. Das Bereichsobjekt ist auch ein Iterator und verfügt über eine geeignete iter- und eine next- Methode.
Mit anderen Worten, ein Objekt, das sowohl die Methode iter als auch die Methode next hat, wird als Iterator bezeichnet.
Mit anderen Worten, der Iterator erfordert eine __iter__
und eine __next__
Methode.
Diese Idee, "eine XX-Methode zu benötigen", wird in Java als Schnittstelle oder in Swift als Protokoll bezeichnet. Wenn Sie die Anfrage nicht erfüllen, erhalten Sie eine Fehlermeldung. Bitte beachten Sie, dass die ** Einschränkung **, die von Benutzern auf Sprachspezifikationsebene verlangt wird, in verschiedenen Situationen wichtig ist.
Die Iter-Funktion konvertiert den übergebenen Wert in einen ** Iterator **. Intern wird die Methode iter des Objekts aufgerufen. Wenn es sich bereits um einen Iterator handelt, wie oben erläutert, gibt er sich selbst zurück.
Andere Objekte als Iteratoren können auch iter- Methoden haben.
list ist kein Iterator, da es keine "next" -Methode gibt. Wenn es jedoch eine "iter" -Methode hat und die Iter-Funktion angewendet wird, gibt es einen Iterator namens ** list_iterator ** zurück.
Und hier heißt das Objekt, das einen Iterator mit der Methode iter zurückgeben kann, ** iterable **. Mit anderen Worten, ein Objekt, das mit der iter-Funktion in einen Iterator konvertiert werden kann, wird als iterable bezeichnet.
Ja, die Liste ist also iterierbar.
Der Iterator selbst ist iterierbar, da er sich in der Iter-Funktion zurückgibt.
Ich werde die internen Umstände der for-Anweisung noch einmal erläutern.
Die for-Anweisung wendet die iter-Funktion auf die nach in angegebene Iterable an, konvertiert sie in einen Iterator und wendet dann die Funktion ** next so weit wie möglich auf diesen Iterator an.
Wenn Sie die nächste Funktion so oft wie möglich anwenden, befinden Sie sich in einer Endlosschleife, wenn Sie kein Ende festlegen, und in vielen Fällen implementieren Sie ein Ende (es gibt auch Endlositeratoren).
Ein Ende bedeutet, dass sich der Status des Objekts bei jeder Verwendung der nächsten Funktion dem Ende nähert.
Hier ist ein konkretes Beispiel.
list_iter = iter([1,2,3,4,5])
a = next(list_iter)
# a => 1
b = next(list_iter)
# b => 2
c = next(list_iter)
# c => 3
d = next(list_iter)
# d => 4
e = next(list_iter)
# e => 5
list ist iterierbar und gibt bei Anwendung der iter-Funktion einen Iterator namens list_iterator zurück.
Wenn die nächste Funktion auf list_iterator angewendet wird, scheint sie das wartende Element auszugeben.
(Sie müssen sich keine Gedanken darüber machen, wie die Implementierung hier ist, aber im Moment enthält list_iterator wahrscheinlich einen Index. Wenn Sie die nächste Funktion anwenden, werden die Elemente des aktuellen Index zurückgegeben und der Index zu + hinzugefügt Wird 1.) sein
Das letzte Element "5" wurde "e" zugewiesen. Was ist, wenn wir hier die nächste Funktion erneut anwenden?
f = next(list_iter)
# => StopIteration
Eine Ausnahme wurde ausgelöst ** StopIteration **. Wenn Sie keine Ausnahmen behandeln, wird das Programm hier beendet.
"So viel wie möglich" bedeutet, bis diese Stopp-Iteration ausgelöst wird.
Mit anderen Worten, wenn innerhalb der for-Anweisung eine Ausnahme namens StopIteration ausgelöst wird, wird der Block verlassen.
Iteratoren sind in vielerlei Hinsicht nützlich, und es gibt keinen einzigen Grund.
Ich denke, es gibt verschiedene Kontexte wie Semantik, Verzögerungsbewertung, Speichereffizienz, Einschränkungen und Verallgemeinerung.
Es ist sehr schwierig, alles zu erklären, daher ist die informativste Geschichte der Vorteil der Speichereffizienz. Es beschreibt auch die Semantik, die ich persönlich mag, und die Vorteile der Verallgemeinerung.
Als Erklärung für die Speichereffizienz erscheint es gut, das Bereichsobjekt als Beispiel zu nehmen.
Dies ist die Antwort auf die erste Frage: "Warum erstellen Sie ein Bereichsobjekt?"
Nehmen wir an, dass die Bereichsfunktion eine Liste erstellt.
In diesem Fall möchten Sie damit im Bereich von 1 bis 100.000.000 umgehen.
Dann wird der Bereich (1, 100000001) als Liste von "[1, 2, ..., 100000000]" erstellt.
Dies übt großen Druck auf das Gedächtnis aus. Wenn die Größe des int-Typs 64 Bit beträgt, beträgt die Größe dieser Liste 6.400.000.000 Bit (= 800.000.000 Byte ≒ 800 MB). (Nicht genau, aber es wird definitiv eine verdammt große sein)
Die Lösung dieses Komprimierungsproblems besteht darin, range zu einem Iterator zu machen.
Insbesondere enthält es "Startwert", "aktueller Wert (= Startwert)" und "Endwert" und gibt bei jedem Aufruf der nächsten Funktion den "aktuellen Wert" und "aktuell" zurück. Sie können es wiederholen, indem Sie dem Wert +1 hinzufügen. Und wenn Sie ** StopIteration ** auslösen, wenn der "aktuelle Wert" zum "Endwert" wird, endet die for-Anweisung. (Insbesondere entspricht es genau dem C-Sprachstil für Anweisungen.)
Ich denke, die Implementierung wird wie folgt aussehen. (Nicht gleichbedeutend mit Reichweite.)
class Range:
def __init__(self, start, end):
self.i = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.i == self.end:
raise StopIteration
else:
x = self.i
self.i += 1
return x
for i in Range(1, 100):
print(i)
# 1
# 2
# ...
# 99
#Hier enden
Der von diesem Iterator zugewiesene Speicher beträgt ungefähr 192 Bit, einschließlich der Größe der internen Werte "self.i", "self.end", "x" usw. (Es ist ein Durcheinander, weil es auf verschiedene Weise kaputt ist, aber es ist definitiv kleiner.)
Im Vergleich zur Liste sind die Entfernung von der Erde zum Mond und die Breite des Tennisplatzes unterschiedlich.
Die Implementierung in einem Iterator spart häufig Speicher, anstatt eine neue Liste zu erstellen.
Dies ist ein Vorteil der Iterator-Speichereffizienz.
Ich persönlich halte es für sehr wichtig, verschiedene Dinge als wiederholbar zu interpretieren.
Zum Beispiel möchte ich, dass Folgendes iterierbar ist:
Dies sollten Elemente nacheinander in der for-Anweisung sein. Ich möchte, dass du rauskommst. Ich habe ein Gefühl.
Selbst in meiner eigenen Klasse gibt es viele Situationen, in denen dies erfrischend erscheint, wenn es mit einer for-Anweisung gedreht werden kann.
Es wäre vielseitiger, wenn iterable als Argument einer Funktion oder etwas angegeben werden könnte.
Beispielsweise sollte jede iterierbare Datei in eine Liste konvertierbar sein. Es ist mühsam, jeden Typ wie Liste (Elemente: Diktat), Liste (Elemente: Tupel) usw. zu definieren. Es wäre also schön, einen Typ mit einem iterablen Beweis angeben zu können.
x = list(iterable)
In der Tat hat Liste eine solche Implementierung.
Als eleganteres Beispiel Heap-Sortierung
sorted_list = list(heap)
Wäre es nicht schön, wenn es so ausgedrückt werden könnte?
Tatsächlich geben Python-Funktionen häufig iterable an. Es gibt so viele Max-Funktionen, Min-Funktionen, Map-Funktionen, Str-Join-Methoden, Reduktionsfunktionen des functools-Moduls und so weiter.
Wenn Sie die abstrakte Idee haben, in Ihrem Unternehmen wiederholbar zu sein, werden Sie möglicherweise gefragt: "Ist dies eine Art iterierbare Geschäftslogik?" (Quelle)
Rundenbasierte Spiele wie Othello können ebenfalls als iterierbar angesehen werden. Daher ist die folgende Implementierung (Ausdruck) möglich.
othello = Othello()
for turn in othello:
point = input()
turn.put(point)
#Wenn das Brett mit Steinen gefüllt ist oder zur Hälfte endet, können Sie die for-Anweisung beenden, indem Sie Stop Iteration auslösen.
Das Konzept des Iterators ist verschiedenen Programmiersprachen als ** Repeater ** gemeinsam und als "Objekt, das für jede Anweisung in einem ** angegeben werden kann **" bekannt. Normalerweise kennen Sie sich mit Iteratoren nicht aus, und Sie neigen dazu zu glauben, dass für jede Anweisung eine Syntax für begrenzte Dinge wie ArrayList vorbereitet ist. In jeder Sprache jedoch, wenn Sie einen Beweis (eine Schnittstelle) implementieren, mit der für jede Ihre eigene Klasse gedreht werden kann Aber Sie können es mit dem für jede Anweisung drehen.
Recommended Posts