2017-02-13 2 views
4

Wenn Sie eine Liste/Tupel/Sequenz durchlaufen, können Sie mit len(...) ermitteln, wie oft die Schleife ausgeführt wurde. Wenn Sie jedoch einen Iterator durchlaufen, können Sie dies nicht tun.Python-Idiom zum Zählen der Schleifenausführung

[Update für Klarheit: Ich denke über endliche Iteratoren für den einmaligen Gebrauch, wo ich Berechnungen auf den Elementen UND zählen sie gleichzeitig durchführen möchte.]

ich derzeit eine explizite Zählervariable wie im folgenden Beispiel verwenden:

def some_function(some_argument): 
    pass 


some_iterator = iter("Hello world") 

count = 0 
for value in some_iterator: 
    some_function(value) 
    count += 1 

print("Looped %i times" % count) 

Da gibt es 11 Zeichen in "Hello world", die erwartete Ausgabe ist hier:

Looped 11 times 

Ich habe berücksichtigte auch diese kürzere Alternative mit enumerate(...), aber ich finde das nicht so klar:

def some_function(some_argument): 
    pass 


some_iterator = iter("Hello world") 

count = 0 # Added for special case, see note below 
for count, value in enumerate(some_iterator, start=1): 
    some_function(value) 

print("Looped %i times" % count) 

[Update als Referenz: @mata entdeckte, dass dieses zweite Beispiel wie ursprünglich geschrieben fehlschlagen würde, wenn der Iterator leer ist. Das Einfügen von count = 0 löst das, oder wir können die for ... else ... Struktur verwenden, um dieses Eckgehäuse zu verarbeiten.]

Es verwendet nicht den Index von enumerate(...) innerhalb der Schleife, sondern die Variable auf den Loop-Count einzustellen, ist fast ein Nebeneffekt. Für mich ist das ziemlich unklar, deshalb bevorzuge ich die erste Version mit dem expliziten Inkrement.

Gibt es eine akzeptierte Pythonic-Methode (ideal für Python 3 und Python 2)?

+0

Ja, und diese Lösung ist "enumerate". Warum halten Sie es nicht für "klar"? Ich weiß nicht, ob es daran gewöhnt ist, sich daran zu gewöhnen, aber 'enumerate 'scheint mir lesenswert und prägnant zu sein ... – schwobaseggl

+3

Es kommt darauf an ... Ich würde sagen, dass die zweite Form mehr Python ist, aber auch deckt nicht genau den gleichen Fall ab, denn wenn der Iterator keine Elemente "count" hat, nachdem die Schleife nicht definiert wurde und Sie einen NameError erhalten, dann müssten Sie einen 'for ... else' verwenden oder den Zähler initialisieren vor der Schleife. – mata

+0

Guter Punkt @mata, wenn der Iterator leer war, wird das zweite Beispiel wie beschrieben fehlschlagen. – peterjc

Antwort

1

Es gibt keine Möglichkeit, die Anzahl der Elemente in einem Iterator zu ermitteln. Denken Sie an diesen Fall

def gen(): 
    a = 1 
    while True: 
     yield a 
     a += 1 
f = gen() 

for value in f: 
    # do something 

Was ist die Größe dieses Iterators? Ein Iterator endet, wenn und wenn, es löst eine StopIteration. Im Gegenteil, wenn Sie über eine Sequenz iterieren, ist die Sequenz bereits vorhanden, so dass ihre Länge bekannt sein kann.

Beide Ansätze, die Sie verwendet haben, sind in Ordnung. am besten hängt von Ihrem Geschmack ab. Eine andere Möglichkeit wäre es, in den meisten Fällen Ihren ersten, traditionellen Ansatz klarer zu machen.

+0

Dies ist ein gutes Beispiel für die Erklärung, warum ich diese Frage stelle - ich habe in der Einleitung darüber geschwiegen, dass Sie '' len (...) '' nicht in einem Iterator verwenden können.Aber Ihre Antwort beantwortet nicht meine Frage, wie man die Anzahl der Items im Iterator am besten zählt, wenn man davon ausgeht, dass man sie in einer for-Schleife verwendet. – peterjc

+0

@peterjc: ok, nur darauf hingewiesen, dass es nicht möglich ist. die Frage bearbeitet –

+0

Danke - das ist jetzt klarer. Ich stimme zu, der "beste" Ansatz wird eine Frage des Geschmacks sein. – peterjc

0

Was ist daran falsch?

len(list(some_iterator)) 
+2

Es funktioniert, aber liest alle Elemente von 'some_iterator' in den Speicher. –

5

Sie den Komfort enumerate mit dem Zähler kombinieren kann, wenn die Schleife eine Zeile durch Zugabe laufen nicht definiert ist:

count = 0 # Counter is set in any case. 
for count, item in enumerate(data, start=1): 
    doSomethingTo(item) 
print "Did it %d times" % count 

Wenn alles, was Sie brauchen es die Anzahl der Elemente zu zählen, ist in ein Iterator, ohne irgendetwas mit den Einzelteilen zu tun, und ohne eine Liste von ihnen zu machen, können Sie es einfach tun:

count = sum(1 for ignored_item in data) # count a 1 for each item 
+1

Wie geschrieben, hat dies einen off durch einen Fehler (standardmäßig '' enumerate'' verwendet '' 0'' um '' count - 1''), weshalb ich das ausführliche extra Argument hatte, um von einem zu starten. – peterjc

+0

Ich kenne den List-Kompressionstrick mit sum, aber in diesem Fall muss ich etwas mit den Items machen (im Beispiel mit '' some_function'' dargestellt). – peterjc

+1

@peterjc: Guter Fang, danke! Ich habe meine Antwort mit "start = 1" aktualisiert. – 9000

2

Sie können alle möglichen Sachen tun, um die Anzahl der Elemente in einem generato zu zählen r, aber in jedem Fall wird der ursprüngliche Generator verschwendet. Erschöpft, um genau zu sein.

length = sum(1 for x in gen) 
length = max(c for c, _ in enumerate(gen, 1)) 
length = len(list(gen)) 
  1. Der erste Weg gezeigt, hier ist wirklich schön, wie es der Fall mit einem leeren Generator kann gut und gibt Null zurück, wie erwartet.
  2. Der zweite Code wird eine Ausnahme auslösen, wenn er einen erschöpften Generator erhält, was nützlich sein kann, wenn er nie erschöpft sein sollte, aber tatsächlich ist, so wird die Ausführung gestoppt und Sie können untersuchen, was schief gelaufen ist.
  3. Dieser Code wird wahrscheinlich viel Speicher verschwenden, wenn die von gen gelieferten Daten zu groß sind, aber es ist einfach zu verstehen, so dass niemand hart nachdenken muss, um zu verstehen, was das bedeutet.

Alle diese werden nur für endliche Generatoren funktionieren.

Wenn Sie die ‚Länge‘ des Iterators berechnen möchten, während sie über ihn Looping, können Sie dies tun:

length = 0 
for length, data in enumerate(gen, 1): 
    # do stuff 

Nun length der Generator auf die Anzahl der Elemente gleich sein wird produziert . Beachten Sie, dass Sie length nicht manuell inkrementieren müssen, da sowohl length als auch data noch verfügbar und nach der Schleifenausführung gültig sind.


EDIT: Wenn Sie eine Funktion für jeden Wert ausführen wollen und ignorieren den Rückgabewert (Sie es, indem Sie eine Liste als einer der Funktionsargumente verarbeiten kann), können Sie dies versuchen:

length = sum(1 | bool(function(x)) for x in gen) 

Damit wird die Länge berechnet, während function auf jedes Element des Generators angewendet wird. Dennoch scheint die Verwendung von enumerate eine bessere Idee.

+0

Keines Ihrer Beispiele ermöglicht die Ausführung von Code, der die Elemente im Generator verwendet (was teuer sein kann oder nur einmal verwendet werden kann), aber Sie enden damit, dass Sie den "enumerate" -Ansatz unterstützen. – peterjc

+0

@peterjc, Ich habe ein Beispiel hinzugefügt, wie Sie Funktionsanwendung in eine meiner ursprünglichen Lösungen integrieren können. – ForceBru

+0

Das ist ein origineller Generator-Ausdruck, der das Zählen mit dem Aufrufen der Funktion auf jedem Element kombiniert, aber aus Geschmackssicht finde ich es zu kompliziert. Die Art meiner Frage bedeutet, Alternativen zu beurteilen, war immer subjektiv. – peterjc

Verwandte Themen