Methode, die bedeutend schneller ist als sum(1 for i in it)
wenn die Iterable lang sein kann (und nicht bedeutend langsamer, wenn die Iterable kurz ist), während das Verhalten des festen Speicher-Overheads beibehalten wird (im Gegensatz zu len(list(it))
), um Swap-Thrashing und Neuzuweisungs-Overhead für größere Eingaben zu vermeiden:
from future_builtins import zip
from collections import deque
from itertools import count
def ilen(it):
cnt = count()
deque(zip(it, cnt), 0)
return next(cnt)
Wie len(list(it))
es führt die Schleife in C - Code auf CPython ( deque
, count
und zip
sind in C alle implementiert); Das Vermeiden der Ausführung von Bytecode pro Schleife ist normalerweise der Schlüssel zur Leistung in CPython.
Es ist überraschend schwierig, faire Testfälle für den Leistungsvergleich zu finden ( list
Cheats, __length_hint__
die für Iterables mit beliebiger Eingabe wahrscheinlich nicht verfügbar sind, itertools
Funktionen, die nicht bereitgestellt werden , verfügen __length_hint__
häufig über spezielle Betriebsmodi, die schneller arbeiten, wenn der Wert in jeder Schleife zurückgegeben wird wird freigegeben / freigegeben, bevor der nächste Wert angefordert wird, was deque
mit maxlen=0
will do). Der Testfall, den ich verwendet habe, bestand darin, eine Generatorfunktion zu erstellen, die eine Eingabe übernimmt und einen Generator auf C-Ebene zurückgibt, dem spezielle itertools
Optimierungen für Rückgabecontainer fehlten, oder __length_hint__
Python 3.3 zu verwenden yield from
:
def no_opt_iter(it):
yield from it
Verwenden Sie dann ipython
%timeit
Magie (ersetzen Sie 100 durch verschiedene Konstanten):
>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))
Wenn die Eingabe nicht groß genug ist, len(list(it))
um Speicherprobleme zu verursachen, dauert meine Lösung auf einer Linux-Box mit Python 3.5 x64 etwa 50% länger als def ilen(it): return len(list(it))
unabhängig von der Eingabelänge.
Für die kleinsten Eingänge bedeuten die Einrichtungskosten für das Aufrufen von deque
/ zip
/ count
/, next
dass es auf diese Weise unendlich länger dauert als def ilen(it): sum(1 for x in it)
(ungefähr 200 ns mehr auf meinem Computer für einen Eingang der Länge 0, was einer Steigerung von 33% gegenüber dem einfachen sum
Ansatz entspricht), aber für Bei längeren Eingaben dauert es ungefähr die Hälfte der Zeit pro zusätzlichem Element. Bei Eingaben der Länge 5 sind die Kosten gleich, und irgendwo im Bereich der Länge 50-100 ist der anfängliche Overhead im Vergleich zur tatsächlichen Arbeit nicht wahrnehmbar. Der sum
Ansatz dauert ungefähr doppelt so lange.
Grundsätzlich sollten Sie diese Lösung verwenden, wenn die Speichernutzung oder Eingaben keine begrenzte Größe haben und Sie mehr Wert auf Geschwindigkeit als auf Kürze legen. Wenn Eingaben begrenzt und klein sind, len(list(it))
ist dies wahrscheinlich am besten, und wenn sie unbegrenzt sind, aber Einfachheit / Kürze zählt, würden Sie verwenden sum(1 for x in it)
.
_
als Variablennamen, da (1) er die Leute verwirrt und sie glauben lässt, dass dies eine spezielle Syntax ist, (2)_
im interaktiven Interpreter kollidiert und (3) mit dem allgemeinen gettext-Alias kollidiert .