Лучший способ для цикла Python 'for'
мы все знаем, что общий способ выполнения оператора определенное количество раз в Python заключается в использовании for петли.
общий способ сделать это,
# I am assuming iterated list is redundant.
# Just the number of execution matters.
for _ in range(count):
pass
Я считаю, что никто не будет спорить, что приведенный выше код является общей реализацией, однако есть и другой вариант. Используя скорость создания списка Python путем умножения ссылок.
# Uncommon way.
for _ in [0] * count:
pass
есть еще и старый while путь.
i = 0
while i < count:
i += 1
Я тестировал время выполнения этих подходов. Вот этот код.
import timeit
repeat = 10
total = 10
setup = """
count = 100000
"""
test1 = """
for _ in range(count):
pass
"""
test2 = """
for _ in [0] * count:
pass
"""
test3 = """
i = 0
while i < count:
i += 1
"""
print(min(timeit.Timer(test1, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test2, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test3, setup=setup).repeat(repeat, total)))
# Results
0.02238852552017738
0.011760978361696095
0.06971727824807639
Я бы не стала инициировать эту тему, если бы была небольшая разница, однако видно, что разница в скорости составляет 100%. Почему Python не поощряет такое использование, если второй метод намного эффективнее? Есть ли лучший способ?
тест выполняется с Windows 10 и Python 3.6.
После @Tim Peters' предложение,
.
.
.
test4 = """
for _ in itertools.repeat(None, count):
pass
"""
print(min(timeit.Timer(test1, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test2, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test3, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test4, setup=setup).repeat(repeat, total)))
# Gives
0.02306803115612352
0.013021619340942758
0.06400113461638746
0.008105080015739174
, который предлагает гораздо лучшую сторону, и это в значительной степени отвечает на мой вопрос.
почему это быстрее, чем range, так как оба являются генераторами. Это потому, что значение никогда не меняется?
3 ответа:
используя
for _ in itertools.repeat(None, count) do somethingэто неочевидный способ получить лучшее из всех миров: крошечное постоянное требование к пространству и отсутствие новых объектов, созданных за итерацию. Под обложками, код C для
repeatиспользует собственный целочисленный тип C (не целочисленный объект Python!), чтобы отслеживать количество оставшихся.по этой причине счетчик должен вписываться в платформу C
ssize_tтип, который обычно не более2**31 - 1на 32-битной коробке, а здесь на 64-битной коробка:>>> itertools.repeat(None, 2**63) Traceback (most recent call last): ... OverflowError: Python int too large to convert to C ssize_t >>> itertools.repeat(None, 2**63-1) repeat(None, 9223372036854775807)который достаточно большой для моих петель ;-)
первый метод (в Python 3) создает объект диапазона, который может перебирать диапазон значений. (Это похоже на объект генератора, но вы можете повторить его несколько раз.) Он не занимает много памяти, потому что он не содержит весь диапазон значений, только текущее и максимальное значение, где он продолжает увеличиваться на размер шага (по умолчанию 1), пока он не достигнет или не пройдет максимум.
сравните размер
range(0, 1000)в размереlist(range(0, 1000)):Попробуй Онлайн!. Первый очень эффективен для памяти; он занимает всего 48 байт независимо от размера, тогда как весь список линейно увеличивается с точки зрения размера.второй метод, хотя и быстрее, занимает ту память, о которой я говорил в прошлом. (Кроме того, кажется, что хотя
0занимает 24 байта иNoneзанимает 16, массивы10000каждого имеют одинаковый размер. Интересный. Наверное, потому, что они указатели)интересно, что
[0] * 10000меньше, чемlist(range(10000))примерно на 10000, что имеет смысл, потому что в первом из них все одно и то же примитивное значение, поэтому его можно оптимизировать.третий тоже хороший, потому что он не требует другого значения стека (в то время как вызов
rangeтребуется еще одно место в стеке вызовов), хотя, поскольку это в 6 раз медленнее, это не стоит того.последний может быть быстрым только потому, что
itertoolsэто круто: P Я думаю, что он использует некоторые C-библиотеки оптимизации, если я правильно помню.
первые два метода должны выделять блоки памяти для каждой итерации, в то время как третий будет просто делать шаг для каждой итерации.
Range-это медленная функция, и я использую ее только тогда, когда мне нужно запустить небольшой код, который не требует скорости, например,
range(0,50). Я думаю, что вы не можете сравнить эти три метода; они совершенно разные.согласно комментарию ниже, первый случай действителен только для Python 2.7, в Python 3 он работает как xrange и не работает выделите блок для каждой итерации. Я проверил его, и он прав.