for
петли не идут назад или вперед. Они просто берут объект итератора для данного итерируемого объекта , затем многократно вызывают __next__()
объект , пока этот метод не вызовет StopIteration
.
Для объектов последовательности итератор просто сохраняет внутренний индекс, который увеличивается каждый раз, когда вызывается __next__
, чтобы получить следующее значение в последовательности. Этот внутренний индекс обычно недоступен.
Так что, если ваш конкретный вариант использования упрощается при «возврате», вам придется создать объект итератора, который предоставляет индекс или иным образом позволяет вам изменить то, какое значение будет создано для следующего __next__
по телефону:
class PositionableSequenceIterator:
def __init__(self, sequence):
self.seq = sequence
self._nextpos = None
@property
def pos(self):
pos = self._nextpos
return 0 if pos is None else pos - 1
@pos.setter
def pos(self, newpos):
if not 0 <= newpos < len(self.seq):
raise IndexError(newpos)
self._nextpos = newpos
def __iter__(self):
return self
def __next__(self):
try:
return self.seq[self.nextpos or 0]
except IndexError:
raise StopIteration
finally:
self.nextpos += 1
так что теперь вы можете сделать
iterator = PositionableSequenceIterator(some_list)
for elem in iterator:
if somecondition:
iterator.pos -= 2
# ...
чтобы пропустить два шага назад.
Однако я бы не ожидал, что это будет быстрее, чем цикл while
. Циклы while
не являются особенно быстрыми, тестирование условия while
на каждой итерации не сильно отличается от вызова iterator.__next__()
, на самом деле. В вашем временном тесте условие while
медленнее, поскольку оно выполняет байт-код Python на каждой итерации (как для условия , так и для увеличения n
в теле цикла), но итератор range()
реализован полностью в C. Приведенный выше класс итератора снова реализует __next__
в коде Python, поэтому будет столь же медленным .
Чтобы продемонстрировать, я могу показать вам, что временные различия полностью обусловлены более медленным условием и телом цикла:
>>> import timeit
>>> count, total = timeit.Timer("n = 0\nwhile n < 10 ** 6:\n n += 1").autorange()
>>> whileloop = total / count
>>> count, total = timeit.Timer("for i in range(10 ** 6):\n pass").autorange()
>>> forloop = total / count
>>> count, total = timeit.Timer("n < 10 ** 6", "n = 10 ** 5 * 5").autorange()
>>> testbelow = total / count
>>> count, total = timeit.Timer("n += 1", "n = 0").autorange()
>>> increment = total / count
>>> count, total = timeit.Timer("nxt()", "nxt = iter(range(1 << 23)).__next__").autorange() # enough room to find a good test range
>>> rangeitnext = total / count
>>> whileloop - forloop # the for loop "speed advantage", per million iterations
0.03363728789991001
>>> (testbelow + increment) - rangeitnext # relative difference per iteration
-9.191804809961469e-08
>>> ((testbelow + increment) - rangeitnext) * 10 ** 9 # microseconds
-91.9180480996147
Таким образом, в этих тестах я могу в лучшем случае доказать, что между каждым шагом итерации цикла есть только 92 микросекунд , с while
быстрее , если это имеет смысл. Это потому, что если я повторю это достаточно часто, это около достигнет разницы (whileloop - forloop) / 10 ** 6
, потому что эти цифры слишком малы, чтобы их действительно волновать.
Обратите внимание, что подобный выше итератор обычно является избыточным. Подавляющее большинство проблем, когда кто-то может захотеть «перемотать» итератор, на самом деле просто хотят отслеживать ранее просмотренные элементы. Это можно сделать тривиально, например, с помощью очереди:
from collections import deque
preceding = deque(maxlen=2)
for item in iterable:
if condition:
# process items in preceding
preceding.append(item)
Вышеуказанные два последних элемента сохраняются на случай, если вам понадобится их обработать.
Или вы можете использовать zip()
и независимые итераторы:
from itertools import islice
twoforward = islice(iterable, 2, None)
for twoback, current in zip(iterable, twoforward):
# twoback and current are paired up at indices i - 2 and i.
Что касается вашей функции euler_differentiate_mod()
, следующее выполняет ту же работу, без необходимости продвигать счетчик while
. Ваша функция в основном вычисляет дельты до 3 раз за итерацию и переходит к следующей итерации, когда у вас есть максимальное изменение в пределах допуска или вы пробовали 3 раза:
def euler_differentiate_mod(
w, bounds=None, delta=1e-3, itern=1000, tols=(10, 0.1), step_mults=(0.1, 10)
):
if bounds is None:
bounds = [0] * len(w)
for _ in range(itern):
for _ in range(3):
deltas = [f(delta, *bounds) for f in w]
maxchange = max(map(abs, deltas[1:])) # ignore the first delta
bounds[:] = [b + d for b, d in zip(bounds, deltas)]
if delta > 1:
delta *= step_mults[0] / maxchange
if tols[1] <= maxchange <= tols[0]:
break
if delta > 1:
if tols[0] < maxchange:
delta *= step_mults[0] / maxchange
elif maxchange < tols[1]:
delta *= step_mults[1] / maxchange