2009-04-02 11 views
12

In Python ist die Schnittstelle eines Iterablen eine Teilmenge der iterator interface. Dies hat den Vorteil, dass sie in vielen Fällen gleich behandelt werden können. Es gibt jedoch einen wichtigen semantischen Unterschied zwischen den beiden, da für eine iterierbare __iter__ ein neues Iteratorobjekt und nicht nur self zurückgegeben wird. Wie kann ich testen, ob ein Iterable wirklich ein Iterator ist und kein Iterator? Konzeptuell verstehe ich iterables als Sammlungen, während ein Iterator nur die Iteration verwaltet (d. H. Die Position verfolgt), aber keine Sammlung selbst ist.Wie kann ich den Unterschied zwischen einem Iterator und einem Iterablen unterscheiden?

Der Unterschied ist zum Beispiel wichtig, wenn man mehrere Male wiederholen möchte. Wenn ein Iterator angegeben wird, funktioniert die zweite Schleife nicht, da der Iterator bereits verbraucht ist und direkt StopIteration auslöst.

Es ist verlockend, für eine next Methode zu testen, aber das scheint gefährlich und irgendwie falsch. Sollte ich nur überprüfen, dass die zweite Schleife leer war?

Gibt es eine Möglichkeit, einen solchen Test auf eher pythische Weise durchzuführen? Ich weiß, dass das klingt wie ein klassischer Fall von LBYL gegen EAFP, also sollte ich vielleicht einfach aufgeben? Oder fehlt mir etwas?

Edit: S.Lott sagt in seiner Antwort unten, dass dies in erster Linie ein Problem zu wollen mehr Durchgänge über den Iterator zu tun, und das sollte man nicht tun in erster Linie. In meinem Fall sind die Daten jedoch sehr groß und müssen je nach Situation mehrfach für die Datenverarbeitung übergeben werden (dazu gibt es absolut keinen Weg).

Das iterierbare Element wird auch vom Benutzer bereitgestellt, und für Situationen, in denen ein einziger Durchgang ausreicht, arbeitet es mit einem Iterator (der z. B. der Einfachheit halber von einem Generator erzeugt wird). Aber es wäre schön, sich gegen den Fall abzusichern, dass ein Benutzer nur einen Iterator bereitstellt, wenn mehrere Durchgänge benötigt werden.

Bearbeiten 2: Eigentlich ist dies ein sehr schönes Beispiel für Abstract Base Classes. Die __iter__ Methoden in einem Iterator und einem iterablen haben denselben Namen, sind aber semantisch verschieden! So hasattr ist nutzlos, aber isinstance bietet eine saubere Lösung.

Antwort

13
'iterator' if obj is iter(obj) else 'iterable' 
+0

Wow, das scheint die Antwort zu sein, nach der ich gesucht habe, danke! Ich werde etwas warten, bevor ich es akzeptiere, falls jemand ein Problem damit aufzeigen kann. – nikow

+0

Nun, das Problem ist ein "verschwendeter" Aufruf an obj .__ iter __(), aber ich sehe keinen anderen zuverlässigen Weg, es zu tun. – vartec

+1

Obwohl ich kein Gegenbeispiel kenne, funktioniert das nicht * garantiert *. – tzot

4

Allerdings gibt es einen wichtigen semantischen Unterschied zwischen den beide ...

Nicht wirklich semantischen oder wichtig. Sie sind beide iterabel - beide arbeiten mit einer for-Anweisung.

Der Unterschied ist zum Beispiel wichtig, wenn man mehrere Male loopen will.

Wann kommt das überhaupt auf? Sie müssen genauer sein. In den seltenen Fällen, in denen Sie zwei Durchgänge durch eine iterierbare Sammlung durchführen müssen, gibt es oft bessere Algorithmen.

Nehmen wir zum Beispiel an, dass Sie eine Liste bearbeiten. Sie können eine Liste ganz nach Wunsch durchlaufen. Warum hast du dich mit einem Iterator anstelle des Iterablen auseinandergesetzt? Okay, das hat nicht funktioniert.

Okay, hier ist eins. Sie lesen eine Datei in zwei Durchgängen und müssen wissen, wie Sie das iterable zurücksetzen können. In diesem Fall ist es eine Datei und seek ist erforderlich; oder schließen und wieder öffnen. Das fühlt sich eklig an.Sie können readlines eine Liste bekommen, die zwei Durchgänge ohne Komplexität erlaubt. Das ist also nicht notwendig.

Warte, was ist, wenn wir eine Datei haben, die so groß ist, dass wir sie nicht alle im Speicher lesen können? Und aus obskuren Gründen können wir auch nicht suchen. Was dann?

Jetzt sind wir auf das Wesentliche von zwei Durchgängen. Beim ersten Durchgang haben wir etwas angehäuft. Ein Index oder eine Zusammenfassung oder etwas. Ein Index enthält alle Daten der Datei. Eine Zusammenfassung ist oft eine Umstrukturierung der Daten. Mit einer kleinen Änderung von "Zusammenfassung" zu "Umstrukturierung" haben wir die Daten der Datei in der neuen Struktur erhalten. In beiden Fällen brauchen wir die Datei nicht - wir können den Index oder die Zusammenfassung verwenden.

Alle "Two-Pass" -Algorithmen können in einem Durchgang des ursprünglichen Iterators oder iterierbar und in einem zweiten Durchgang einer anderen Datenstruktur geändert werden.

Dies ist weder LYBL noch EAFP. Dies ist ein Algorithmusentwurf. Sie müssen einen Iterator nicht zurücksetzen - YAGNI.


bearbeiten

Hier ist ein Beispiel eines Iterators/iterable Ausgabe. Es ist einfach ein schlecht entworfener Algorithmus.

it = iter(xrange(3)) 
for i in it: print i,; #prints 1,2,3 
for i in it: print i,; #prints nothing 

Dies ist trivialerweise behoben.

it = range(3) 
for i in it: print i 
for i in it: print i 

Das "mehrere Male parallel" ist trivialerweise behoben. Schreiben Sie eine API, die erfordert eine iterable. Und wenn jemand sich weigert, die API-Dokumentation zu lesen oder sich weigert, ihr nach dem Lesen zu folgen, bricht ihr Zeug. So wie es sollte.

Die "nette Absicherung gegen den Fall, dass ein Benutzer nur einen Iterator bereitstellt, wenn mehrere Durchgänge benötigt werden" sind Beispiele für wahnsinnige Leute, die Code schreiben, der unsere einfache API durchbricht.

Wenn jemand verrückt genug ist, am meisten zu lesen (aber nicht alle der API doc) und einen Iterator zur Verfügung stellen, wenn ein iterable erforderlich war, müssen Sie diese Person zu finden und ihnen beizubringen, (1), wie all das lesen API-Dokumentation und (2) folgen Sie der API-Dokumentation.

Das Problem "Schutz" ist nicht sehr realistisch. Diese verrückten Programmierer sind bemerkenswert selten. Und in den wenigen Fällen, wenn es auftritt, Sie wissen, wer sie sind und kann ihnen helfen.


Edit 2

Das "wir haben die gleiche Struktur mehrfach lesen" Algorithmen sind ein grundsätzliches Problem.

Tun Sie dies nicht.

for element in someBigIterable: 
    function1(element) 
for element in someBigIterable: 
    function2(element) 
... 

Tun Sie dies stattdessen.

for element in someBigIterable: 
    function1(element) 
    function2(element) 
    ... 

Oder betrachten Sie so etwas.

for element in someBigIterable: 
    for f in (function1, function2, function3, ...): 
     f(element) 

In den meisten Fällen sind diese Art von „Pivot“ Ihre Algorithmen führen zu einem Programm, das einfacher sein könnte, zu optimieren und könnte eine Nettoverbesserung in der Leistung.

+0

Was ist mit Mehrfachnutzen parallel? Z.B. mehrere Threads, die über dieselbe Sammlung iterieren? Oder sogar ein Thread, wie eine leicht vorgestellte, naive Implementierung von "Hat diese Sammlung das gleiche Element zweimal?". – Edmund

+0

Danke, ich fügte der Frage eine Erklärung hinzu. Du hast einen gültigen Punkt, aber in meinem Fall glaube ich, dass das nicht funktioniert. – nikow

+0

"bemerkenswert selten". Ich stimme nicht zu, Programmierer, die iterator nicht iterierbar nennen können, sind keineswegs selten. "Sie wissen wer sie sind und ihnen helfen können." Das ist normalerweise nicht Ihre Aufgabe, und in der Gesellschaft, die "ihnen hilft", würde nicht sehr gut wahrgenommen, besonders wenn es eine andere Abteilung ist. – vartec

0

Wegen Duck Typing Python,

Jedes Objekt ist iterable wenn es die next() und __iter__() Methode liefert selbst definiert.

Wenn das Objekt selbst muß nicht die next() Methode haben, kann die __iter__() jedes Objekt zurückgeben, die eine next() Methode hat

Sie diese Frage finden konnte Iterability in Python

+0

Versuchen Sie Folgendes: Klasse A (Objekt): def __iter __ (self): zurück iter ([1,2,3]) def next (self): yield 7 – vartec

+0

Eigentlich ist dies ein Problem der Ente tippen: Es kann eine Semantik verbergen/konzeptioneller Unterschied. Es erlaubt uns, für i in Bereich (3) statt für i in iter (Bereich (3)) zu schreiben, kann aber subtile Probleme verursachen. – nikow

+0

Sorry, ich habe den Punkt nicht genau verstanden? Etwas stimmt nicht? –

2
import itertools 

def process(iterable): 
    work_iter, backup_iter= itertools.tee(iterable) 

    for item in work_iter: 
     # bla bla 
     if need_to_startover(): 
      for another_item in backup_iter: 

Dieser verdammte Zeitmaschine zu sehen, dass Raymond hat sich von Guido geliehen ...

Verwandte Themen