Wie überprüfe ich, ob ein Iterable mehr als einen Durchgang erlaubt?

8

Wie kann ich in Python 3 überprüfen, ob ein Objekt ein Container ist (und nicht ein Iterator, der nur einen Durchgang erlaubt)?

Hier ist ein Beispiel:

%Vor%

Offensichtlich funktioniert die Funktion renormalize , wenn sie einen Generatorausdruck empfängt, nicht wie vorgesehen. Es nimmt an, dass es mehrmals durch den Container iterieren kann, während der Generator nur einen Durchlauf erlaubt.

Idealerweise würde ich das gerne tun:

%Vor%

Wie kann ich is_container implementieren?

Ich nehme an, ich könnte überprüfen, ob das Argument leer ist, gerade als wir damit beginnen, den zweiten Durchlauf zu machen. Dieser Ansatz funktioniert jedoch nicht für kompliziertere Funktionen, bei denen es nicht offensichtlich ist, wann genau der zweite Durchlauf beginnt. Außerdem würde ich die Validierung lieber am Eingang der Funktion vornehmen, als tief in der Funktion (und umherschalten, wenn die Funktion geändert wird).

Ich kann natürlich die Funktion renormalize so umschreiben, dass sie mit einem One-Pass-Iterator korrekt funktioniert. Dazu müssen die Eingabedaten in einen Container kopiert werden. Der Leistungseinfluss beim Kopieren von Millionen großer Listen "nur für den Fall, dass sie keine Listen sind" ist lächerlich.

EDIT: Mein ursprüngliches Beispiel verwendet eine weighted_average Funktion:

%Vor%

Aber es war nicht das beste Beispiel, da die Version von weighted_average , die neu geschrieben wurde, um einen einzelnen Durchlauf zu verwenden, ohnehin besser ist:

%Vor%     
max 24.01.2012, 20:04
quelle

3 Antworten

4

Obwohl alle iterables Collections unterlassen sollten.Ich verhandelbar, leider nicht alle. Hier ist eine Antwort basierend darauf, welche Schnittstelle die Objekte implementieren, anstatt was sie deklarieren.

Kurze Antwort:

Ein "Container", wie Sie ihn nennen, dh eine Liste / ein Tupel, die mehr als einmal iteriert werden kann, anstatt ein Generator zu sein, der erschöpft sein wird, implementiert normalerweise __iter__ und __getitem__ . Daher können Sie dies tun:

%Vor%

Lange Antwort:

Sie können jedoch ein iterables erstellen, das nicht ausgeschöpft wird und getitem nicht unterstützt. Zum Beispiel eine Funktion, die Primzahlen erzeugt. Sie könnten die Generierung oft wiederholen, wenn Sie möchten, aber eine Funktion zum Abrufen der 1065. Primzahl zu haben, würde eine Menge Berechnung erfordern, also möchten Sie das vielleicht nicht unterstützen. : -)

Gibt es also einen "zuverlässigeren" Weg?

Nun, alle iterables implementieren eine Funktion __iter__ , die einen Iterator zurückgibt. Die Iteratoren haben eine __next__ -Funktion. Dies wird beim Iterieren verwendet. Der Aufruf von __next__ wiederholt am Ende den Iterator.

Wenn es also eine Funktion __next__ hat, ist es ein Iterator und wird erschöpft sein.

%Vor%

Iterables, die noch keine Iteratoren sind, haben keine __next__ -Funktion, sondern implementieren eine __iter__ -Funktion, die ein iterables zurückgibt:

%Vor%

So können Sie überprüfen, ob das Objekt __iter__ hat, aber nicht __next__ .

%Vor%

Iterators hat auch eine Funktion __iter__ , die self zurückgibt.

%Vor%

Daher können Sie diese Varianten der Überprüfung tun:

%Vor%

Dies würde fehlschlagen, wenn Sie ein Objekt implementieren, das einen zerbrochenen Iterator zurückgibt, eines, das nicht selbst zurückgibt, wenn Sie iter () erneut aufrufen. Aber dann macht dein Code (oder der Code eines Drittanbieters) tatsächlich etwas falsch.

Es hängt jedoch davon ab, einen Iterator zu erstellen und somit die Objekte __iter__ aufzurufen, was theoretisch Nebenwirkungen haben kann, während die obigen hasattr-Aufrufe keine Nebenwirkungen haben sollten. OK, also ruft getattribute auf. Aber Sie können das so beheben:

%Vor%

Dieser ist einigermaßen sicher und sollte in allen Fällen funktionieren, außer wenn das Objekt __next__ oder __iter__ dynamisch auf __getattribute__ Aufrufe generiert, aber wenn Sie das tun, sind Sie verrückt. : -)

Instinktiv wäre meine bevorzugte Version iter(o) is o , aber ich musste das nie tun, also basiert das nicht auf Erfahrung.

    
Lennart Regebro 25.01.2012, 10:26
quelle
3

Sie können die im Modul collections definierten abstrakten Basisklassen verwenden, um zu überprüfen, ob it eine Instanz von collections.Iterator ist.

%Vor%

Persönlich finde ich jedoch, dass Ihre iteratorfreundliche Version des gewichteten Durchschnitts viel einfacher zu lesen ist als die Multiple List Comprehension / Sum-Version. : -)

    
stderr 24.01.2012 20:23
quelle
1

Der beste Weg wäre, die abstrakte Basisklasseninfrastruktur zu verwenden:

%Vor%     
LBarret 24.01.2012 20:53
quelle