Обоснование Python предпочтительнее для синтаксиса

В чем смысл обоснованного использования конструкторов цикла for i in xrange(...) -style в Python? Для простых целых циклов разница в надбавке существенна. Я провел простой тест с использованием двух частей кода:

Файл idiomatic.py:

#!/usr/bin/env python

M = 10000
N = 10000

if __name__ == "__main__":
    x, y = 0, 0
    for x in xrange(N):
        for y in xrange(M):
            pass

Файл cstyle.py:

#!/usr/bin/env python

M = 10000
N = 10000

if __name__ == "__main__":
    x, y = 0, 0
    while x < N:
        while y < M:
            y += 1
        x += 1

Результаты профилирования были следующими:

bash-3.1$ time python cstyle.py

real    0m0.109s
user    0m0.015s
sys     0m0.000s

bash-3.1$ time python idiomatic.py

real    0m4.492s
user    0m0.000s
sys     0m0.031s

Я могу понять, почему версия Pythonic работает медленнее - я полагаю, что она имеет много общего с вызовом xrange N раз, возможно, это может быть устранено, если есть способ перемотать генератор. Однако с этой разницей во времени исполнения, почему вы предпочитаете использовать версию Pythonic?

Изменить: Я снова провел тесты, используя код, предоставленный г-ном Мартелли, и теперь результаты были действительно лучше:

Я думал, что перечислил выводы из этой темы:

1) Много кода в области модуля - плохая идея, даже если код заключен в блок if __name__ == "__main__":.

2) * Любопытно, что изменение кода, принадлежащего thebadone к моей неправильной версии (позволяя y расти без перезагрузки), создало небольшую разницу в производительности даже для больших значений M и N.

Ответ 1

Здесь правильное сравнение, например. в loop.py:

M = 10000
N = 10000

def thegoodone():
   for x in xrange(N):
       for y in xrange(M):
           pass

def thebadone():
    x = 0
    while x < N:
        y = 0
        while y < M:
            y += 1
        x += 1

Весь существенный код должен всегда находиться в функциях - помещение сто миллионов петель на верхнем уровне модуля показывает безрассудное игнорирование производительности и делает издевательство над любыми попытками измерения указанной производительности.

Как только вы это сделали, вы увидите:

$ python -mtimeit -s'import loop' 'loop.thegoodone()'
10 loops, best of 3: 3.45 sec per loop
$ python -mtimeit -s'import loop' 'loop.thebadone()'
10 loops, best of 3: 10.6 sec per loop

Итак, правильно измеренный, плохой способ, который вы защищаете, примерно в 3 раза медленнее, чем хороший способ, который продвигает Python. Надеюсь, это заставит вас пересмотреть свою ошибочную пропаганду.

Ответ 2

Вы забыли reset y до 0 после внутреннего цикла.

#!/usr/bin/env python
M = 10000
N = 10000

if __name__ == "__main__":
    x, y = 0, 0
    while x < N:
        while y < M:
            y += 1
        x += 1
        y = 0

ed: 20.63s после исправления по сравнению с 6.97 с использованием xrange

Ответ 3

полезно для итерации над структурами данных

Синтаксис for i in ... отлично подходит для итерации над структурами данных. На языке более низкого уровня вы обычно выполняете итерацию по массиву, индексируемому с помощью int, но с синтаксисом python вы можете исключить шаг индексирования.

Ответ 4

это не прямой ответ на вопрос, но я хочу открыть диалог немного больше на xrange(). две вещи:

а. что-то не так с одним из операторов OP, которые никто еще не исправил (да, в дополнение к ошибке в коде сброса y):

"Я предполагаю, что он имеет много общего с вызовом xrange N раз..."

в отличие от традиционного подсчета for циклов, Python больше похож на оболочку foreach... зацикливая над итерабельным. поэтому xrange() вызывается ровно один раз, а не "N раз".

В. xrange() - это имя этой функции в Python 2. она заменяется и переименовывается в range() в Python 3, поэтому имейте это в виду при портировании. если вы еще не знаете, xrange() возвращает итератор (-подобный объект), а range() возвращает списки. так как последний более неэффективен, он устарел в пользу xrange(), который более удобен для памяти. обходной путь в Python 3, для всех тех, кому нужен список, list(range(N)).

Ответ 5

Я повторил тест из @Alex Martelli. Идиоматический цикл в 5 раз быстрее, чем цикл while:

python -mtimeit -s'from while_vs_for import while_loop as loop' 'loop(10000)'
10 loops, best of 3: 9.6 sec per loop
python -mtimeit -s'from while_vs_for import for_loop as loop'   'loop(10000)'
10 loops, best of 3: 1.83 sec per loop

while_vs_for.py

def while_loop(N):
    x = 0
    while x < N:
        y = 0
        while y < N:
            pass
            y += 1
        x += 1

def for_loop(N):
    for x in xrange(N):
        for y in xrange(N):
            pass

На уровне модуля:

$ time -p python for.py
real 4.38
user 4.37
sys 0.01
$ time -p python while.py
real 14.28
user 14.28
sys 0.01