有什么办法可以使Python for循环退一步吗?

时间:2019-03-27 15:31:58

标签: python loops for-loop

我希望找到一种方法来有效地模仿从while循环到for循环的相同可能行为,以使循环“停顿”或在满足条件的情况下退一步。原因是,我正在尝试实现具有大量迭代的计算,根据forwhile循环比%%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

1 个答案:

答案 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()