Концевая петля со счетчиком и условием
В 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.