我希望找到一种方法来有效地模仿从while
循环到for
循环的相同可能行为,以使循环“停顿”或在满足条件的情况下退一步。原因是,我正在尝试实现具有大量迭代的计算,根据for
,while
循环比%%timeit
循环快4倍。 / p>
%%timeit
n = 0
while n < 1e7:
n += 1
# 1.96 s +- 211 ms per loop
%%timeit
for i in range(int(1e7)):
pass
# 399 ms +- 28.1 ms per loop
对于那些提到这是“不公平”比较的人来说,while
循环如果没有n += 1
语句就无法循环,而for
循环却可以。因此,对于while循环,该行是不必要的;对于for循环,该行是不必要的。因此,这并不是“不公平”的比较。
我的特定问题/代码:
def euler_differentiate_mod(w, bounds = None, delta = 1e-3, itern = 1e3,
force = False,
tols = [10, 0.1], step_mults = [0.1, 10]):
if bounds is None:
bounds = [0]*len(w)
if not force and itern >= 1e9:
raise OverflowError("number of iterations is too big: {!s}" + "\n" + \
"you can ignore this error by setting the `force` kwarg to `False`"
.format(itern))
itern = int(itern)
var = bounds
n = 1
while n < itern: # used to be: for n in range(1, itern+1):
pvar = copy.deepcopy(var)
for i,_ in enumerate(var): # compute new variables
var[i] += w[i](*[delta]+[pvar[j] for j in range(len(pvar))])
fchanges = [abs(var[i]-pvar[i]) for i in range(1, len(var))]
try:
if len(check) > 2:
n += 1
except:
check = []
if max(fchanges) > tols[0]: # big change -> reduce delta
try:
check.append(n)
except:
check = []
delta *= step_mults[0] if delta <= 1 else 1
elif max(fchanges) < tols[1]: # small change -> increase delta
try:
check.append(n)
except:
check = []
delta *= step_mults[1] if delta <= 1 else 1
else:
check = False
n += 1
return None
答案 0 :(得分:6)
for
循环不会来回移动。他们只需将iterator object用作给定的iterable object,然后重复调用__next__()
object,直到该方法引发StopIteration
。
对于序列对象,迭代器仅保留一个内部索引,该内部索引每次调用__next__
以获得序列中的下一个值时都会递增。通常无法访问该内部索引。
因此,如果通过“返回”简化了您的特定用例,则必须创建一个公开该索引的迭代器对象,否则,您可以更改下一个{{1 }}通话:
__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__()
条件较慢,因为它在每次迭代时都执行Python字节码(条件和都使循环主体中的while
递增),但是{ {1}}迭代器完全用C实现。上面的迭代器类再次在Python代码中实现了n
,因此将一样慢。
为证明这一点,我可以向您展示时序差异完全是由于条件和循环体速度较慢所致:
range()
因此,在这些测试中,我最多可以证明每个循环迭代步骤之间只有92个微秒,并且__next__
更快, 。这是因为,如果我经常重复此步骤,就会大约达到>>> 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
的差异,因为这些数字实在太小了,无法真正在乎。
请注意,像上面这样的迭代器通常是多余的。人们可能希望“倒带”迭代器的绝大多数问题实际上只是想跟踪以前看到的项目。您也可以使用其他选项(例如队列)来简单地做到这一点:
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.
计数器。您的函数基本上每次迭代最多可计算3次增量,并且在公差范围内发生最大变化或尝试3次时,将移至下一次迭代:
euler_differentiate_mod()