метод, который значительно быстрее, чем sum(1 for i in it)
, когда итерация может быть долгой (а не по значению медленнее, когда итерируемый короткий), сохраняя при этом фиксированной памяти накладного поведения (в отличие от len(list(it))
), чтобы избежать подкачек обмолота и перераспределения накладных расходов для больших входов:
# 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)
Как len(list(it))
он выполняет цикл в коде C на CPython (deque
count
, и zip
все реализованы в C); избегая выполнения байтового кода за цикл, как правило, является ключом к производительности в CPython.
Это удивительно трудно придумать справедливые тестовые случаи для сравнения производительности (list
читы использования __length_hint__
, которая не является, вероятно, будет доступны для любого входных итерируемых, itertools
функции, которые не обеспечивают __length_hint__
часто имеет специальные режимы, которые работают быстрее, когда значение, возвращаемое в каждом цикле, освобождается до того, как будет запрошено следующее значение, которое будет deque
с maxlen=0
). Тест я использовал, чтобы создать функцию генератора, который будет принимать входной сигнал и возвращает генератор уровня C, что не хватало специального itertools
возврата контейнеров оптимизации или __length_hint__
, используя Python 3,3-х yield from
:
def no_opt_iter(it):
yield from it
Затем с помощью ipython
%timeit
магия (подставляя разные константы 100):
>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))
Если вход не настолько велик, что len(list(it))
может вызвать проблемы с памятью, на коробке Linux работает Python 3.5 x64, мое решение занимает около 50% больше тха n def ilen(it): return len(list(it))
, независимо от входной длины.
Для самых маленьких входов, стоимость установки для вызова deque
/zip
/count
/next
означает, что он принимает бесконечно больше этот путь, чем def ilen(it): sum(1 for x in it)
(около 200 нс больше на моей машине для длины 0 ввода, который представляет собой 33% увеличение по сравнению с простым подходом sum
), но для более длинных входов он работает примерно в половине времени на дополнительный элемент; для длины 5 входов стоимость эквивалентна, а где-то в диапазоне 50-100, начальные накладные расходы незаметны по сравнению с реальной работой; подход sum
занимает примерно в два раза больше.
В принципе, если вопросы использования памяти или ввода не имеют ограниченного размера, и вы заботитесь о скорости больше, чем краткость, используйте это решение. Если входы ограничены и малы, len(list(it))
, вероятно, лучше всего, и если они неограничены, но простота/краткость подсчитываются, вы должны использовать sum(1 for x in it)
.
Пожалуйста, не используйте '_' в качестве имени переменной, потому что (1) она стремится запутать людей, заставляя их думать, это какой-то специальный синтаксис, (2) сталкивается с' _' в интерактивный интерпретатор и (3) сталкивается с общим псевдонимом gettext. –
@Sven: Я использую '_' все время для неиспользуемых переменных (привычка от программирования Prolog и Haskell). (1) является основанием для того, чтобы просить об этом в первую очередь. Я не рассматривал (2) и (3), спасибо за указание их! –
duplicated: http://stackoverflow.com/questions/390852/is-there-any-built-in-way-to-get-the-length-of-an-iterable-in-python – tokland