Концевая петля со счетчиком и условием

В Python я могу реализовать цикл с шаговым счетчиком и условие остановки как классический случай для цикла:

for i in range(50):
    result = fun(i)
    print(i, result)
    if result == 0: 
        break

где fun(x) - некоторая произвольная функция от целых чисел до целых чисел.

Я всегда сомневаюсь, что это лучший способ его кодировать (Pythonically и с точки зрения удобочитаемости и эффективности)). лучше запустить его как while loop:

i = 0
result = 1
while result != 0 and i < 50:
    result = fun(i)
    print(i, result)
    i += 1

какой подход лучше? В частности - меня беспокоит использование оператора break, который не чувствует себя хорошо.

Ответ 1

Цикл for немного более эффективен, чем while, потому что range() реализован в C, в то время как операция += интерпретируется и требует больше операций и создания/уничтожения объектов. Вы можете проиллюстрировать разницу в производительности с помощью модуля timeit, например:

from timeit import timeit

def for_func():
    for i in range(10000):
        result = int(i)
        if result == -1: 
            break

def while_func():
    i = 0
    result = 1
    while result != -1 and i < 10000:
        result = int(i)
        i += 1


print(timeit(lambda: for_func(), number = 1000))
# 1.03937101364
print(timeit(lambda: while_func(), number = 1000))
# 1.21670079231 

Цикл for, возможно, больше Pythonic в подавляющем большинстве случаев, когда вы хотите перебирать итерируемый объект. Кроме того, чтобы процитировать Python wiki: "Поскольку цикл for в Python настолько силен, хотя редко используется, за исключением случаев, когда пользовательский ввод обязательный". Нет ничего не-Pythonic об использовании инструкции break как таковой.

Читаемость в основном субъективна, я бы сказал, что цикл for также читаем, но, вероятно, это зависит от вашего предыдущего опыта программирования и опыта.

Ответ 2

Существует небольшая разница в производительности. Поскольку цикл while выполняет дополнительную логическую проверку на каждой итерации, она немного медленнее.

Однако для большинства функций, которые вы хотели бы оценить, есть альтернатива использованию цикла в первую очередь, что намного быстрее. См. Пример, приведенный ниже. И я уверен, что существует более оптимальная реализация.

import time

def fun(step):
    return step == 50000000

t1 = time.time()
for i in range(500000000):
    result = fun(i)
#    print(i, result)
    if result == 1: 
        break
print("time elapsed", time.time() - t1)

t2 = time.time()
i = 0
result = 0
while result != 1 and i < 50000000:
    result = fun(i)
#    print(i, result)
    i += 1

print("and here", time.time() - t2)

import numpy as np

t3 = time.time()

foo = np.arange(500000000)
i = np.where(foo == 50000000)
print("Alternative", time.time()-t3)

время, прошедшее 11.082087516784668

     

и здесь 14.429940938949585

     

Альтернатива 1.4022133350372314

Если вы хотите/должны использовать цикл, однако, for-loop - это, в общем, более путичный путь, как объясняется в этом приятном ответе на соответствующий вопрос

EDIT: Крис Рэндс ниже объясняет это с точки зрения C-кода и использует более точный (хотя на таких макроуровнях, как я заставил этот пример быть, это не имеет особого значения). Также прочитайте его ответ.

Ответ 3

Лучший способ - не полностью зацикливаться. itertools для спасения:

from itertools import takewhile
it = ((x, f(x)) for x in range(50)) # first you define the resulting view
it = takewhile(lambda y: y[1] != 0, it) # define when to stop iterating
list(map(print, it)) # if you want to print it

+ было бы намного быстрее, чем цикл

Ответ 4

Посмотрев чисто на байт-код, While-loop получил 30 строк, а for-loop получил 26 строк.

В конце концов, на самом деле это не так важно, но я лично предпочитаю версию for-loop, поскольку ее легче читать.

http://mergely.com/9P1cfnTr/

Код, который я использовал:

def fun(i):
    i = i + 1
    return i

def test1():
    i = 0
    result = 1
    while result != 0 and i < 50:
        result = fun(i)
        print(i, result)
        i += 1

def test2():
    for i in range(50):
        result = fun(i)
        print(i, result)
        if result == 0: 
            break

Ответ 5

Я уверен, что вы не должны заботиться о производительности, когда вы сравниваете циклы while и для, поэтому основной вопрос касается читаемости.

в первую очередь, конечно,

for i in range(50):
    result = fun(i)
    print(i, result)
    if result == 0: 
        break

намного лучше, чем

i = 0
result = 1
while result != 0 and i < 50:
    result = fun(i)
    print(i, result)
    i += 1

потому что вам не нужно учитывать некоторые C-подобные переменные, когда вы читаете простой цикл

Решение

@quidkid довольно элегантно, но это хороший момент, когда

it = takewhile(lambda y: y[1] != 0, ((x, f(x)) for x in range(50)))

работает только на довольно простых примерах и потеряет удобочитаемость, поскольку вам потребуется добавить дополнительную функциональность

Как я вижу, основная проблема в вашем коде заключается в том, что ваша функция не чиста, поэтому, возможно, вы должны сделать что-то вроде этого:

def some_generator():
    for i in range(50):
        result = fun(i)
        yield result, i
        if result == 0:
            return 

for x in some_generator():
    print x
    do_something_else_with(x[1])

Ответ 6

Взяв функциональный подход, создайте локальное замыкание, которое выполняет действие и возвращает значение предиката завершения. Затем перебирайте входы до конца.

def action(i):
    result = fun(i)
    print(i, result)
    return result == 0

list(itertools.takewhile(action, range(50)))  # non-pythonic wasted list

Другим способом прекращения последовательности действий является:

terminated = reduce(lambda done, i: done or action(i), range(50), False)  # extra unnecessary iterations

Похоже, мы вернулись к выражению break для pythonicness

for i in range(50):
    if action(i):
        break

Ответ 7

Если эффективность - это то, что вы ищете, используйте функции генератора

In [1]: def get_result():                            
    ...:     for i in xrange(50):                            
    ...:         result = fun(i)
    ...:         yield result, i
    ...:         if result == 0:
    ...:             break
    ...:         

In [2]: generator_func = get_result()

In [3]: for result, i in generator_func:
    ...:     print result, i
    ...:   

Результат% timeit в ipython

Самый медленный пробег занял 6,15 раз дольше, чем самый быстрый. Это могло, это может означает, что промежуточный результат кэшируется. 10000 циклов, лучше всего 3: 30,9 мкс на петлю

Ответ 8

Вам НЕ нужен оператор break вообще... для циклов автоматически прекращается. Все что вам нужно:

for i in range(50):
    result = fun(i)
    print(i, result)

Это всегда будет более читаемым imho.