metoda, która jest znacząco szybsza niż sum(1 for i in it)
gdy iterable mogą być długie (i nie znacząco wolniej gdy iterable jest krótki), przy zachowaniu stałej pamięci napowietrznych zachowanie (w przeciwieństwie len(list(it))
), aby uniknąć wymiany lanie i realokacji narzut dla większych nakładów:
# On Python 2 only, get zip that lazily generates results instead of returning list
from future_builtins import zip
from collections import deque
from itertools import count
def ilen(it):
# Make a stateful counting iterator
cnt = count()
# zip it with the input iterator, then drain until input exhausted at C level
deque(zip(it, cnt), 0) # cnt must be second zip arg to avoid advancing too far
# Since count 0 based, the next value is the count
return next(cnt)
jak len(list(it))
wykonuje pętlę w kodzie C na CPython (wszystkie deque
, count
i zip
są realizowane w c); unikanie wykonywania kodu bajtowego na pętlę jest zwykle kluczem do wydajności w CPython.
Jest zaskakująco trudno wymyślić uczciwych przypadków testowych dla porównania wydajności (list
cheaty wykorzystujące __length_hint__
który nie może być dostępna dla dowolnych iterables wejściowych, itertools
funkcje, które nie zapewniają __length_hint__
często mają specjalne tryby pracy, które działają szybciej, gdy zwrócona wartość w każdej pętli zostanie zwolniona, zanim zażąda kolejnej wartości, co zrobi deque
z maxlen=0
). W przypadku badania że użyto utworzyć funkcję generatora, która weźmie wejście i powrót generator poziomu C pozbawionymi specjalny itertools
optymalizacji pojemnik zwrotny lub __length_hint__
używając Pyton 3,3 na yield from
:
def no_opt_iter(it):
yield from it
Następnie przy ipython
%timeit
magia (podstawiając różne stałe na 100):
>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))
Gdy wejście nie jest na tyle duża, że len(list(it))
spowodowałoby problemy z pamięcią, na Linuksie systemem Pythona 3.5 x64, moje rozwiązanie zajmuje około 50% dłużej tha n def ilen(it): return len(list(it))
, niezależnie od długości wejścia.
dla najmniejszych nakładów, koszty instalacji zadzwonić deque
/zip
/count
/next
Oznacza to trwa nieskończenie dłużej w ten sposób niż def ilen(it): sum(1 for x in it)
(około 200 ns więcej na moim komputerze na długość 0 wejściu, co jest 33% wzrost w stosunku do prostego podejścia sum
), ale przy dłuższych nakładach trwa około połowę czasu na dodatkowy element; dla długości 5 wejść, koszt jest równoważny, a gdzieś w zakresie długości 50-100, początkowy narzut jest niezauważalny w porównaniu do prawdziwej pracy; podejście sum
zajmuje około dwa razy dłużej.
Zasadniczo, jeśli użycie pamięci ma znaczenie lub dane wejściowe nie mają dużego rozmiaru i zależy Ci na szybkości większej niż zwięzłość, skorzystaj z tego rozwiązania. Jeśli dane wejściowe są ograniczone i niewielkie, prawdopodobnie najlepiej jest uzyskać len(list(it))
, a jeśli są nieograniczone, ale liczy się prostota/zwięzłość, użyjemy sum(1 for x in it)
.
Proszę nie używać '_' jako nazwy zmiennej, ponieważ (1) ma tendencję do mylić ludzi, czyniąc z nich, że to jest jakiś rodzaj specjalnej składni, (2) zderza się z' _' w Interaktywny interpreter i (3) koliduje ze wspólnym aliasem Gettext. –
@Sven: Cały czas używam '_' dla nieużywanych zmiennych (nawyk z programowania Prolog i Haskell). (1) jest powodem, dla którego należy to przede wszystkim zadać. Nie uważałem (2) i (3), dziękuję za wskazanie ich! –
powielone: http://stackoverflow.com/questions/390852/is-there-any-built-in-way-to-get-tength-of-anable-in-python – tokland