Лучший способ для цикла 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 и не работает выделите блок для каждой итерации. Я проверил его, и он прав.