测试在循环内不会改变的条件

时间:2012-06-20 08:38:47

标签: python optimization loops

有时我必须检查一些在循环内没有改变的条件,这意味着在每次迭代中都会对测试进行评估,但我认为这不是正确的方法。

我认为因为条件在循环内部没有变化,所以我应该只在循环外测试一次,但是我必须“重复自己”并且可能多次写入相同的循环。这是一个显示我的意思的代码:

#!/usr/bin/python

x = True      #this won't be modified  inside the loop
n = 10000000

def inside():
    for a in xrange(n):
        if x:    #test is evaluated n times
            pass
        else:
            pass

def outside():
    if x:        #test is evaluated only once
        for a in xrange(n):  
            pass
    else:
        for a in xrange(n):
            pass

if __name__ == '__main__':
    outside()
    inside()

在前面的代码上运行cProfile给出了以下输出:

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.542    0.542    0.542    0.542 testloop.py:5(inside)
        1    0.261    0.261    0.261    0.261 testloop.py:12(outside)
        1    0.000    0.000    0.803    0.803 testloop.py:3(<module>)

这表明,显然,在循环外进行一次测试可以提供更好的性能,但是我必须编写两次相同的循环(如果有elif s,可能会更多。)

我知道在大多数情况下这种性能无关紧要,但我需要知道编写这种代码的最佳方法是什么。例如,有没有办法告诉python只评估测试一次?

感谢任何帮助,谢谢。

编辑:

实际上,经过一些测试后,我现在确信性能上的差异主要受到循环内执行的其他代码的影响,而不是测试评估。所以现在我坚持使用第一种形式,这种形式更具可读性,并且更适合以后的调试。

7 个答案:

答案 0 :(得分:5)

首先,您的示例之间性能差异的一个主要组成部分是查找全局的时间。如果我们将其捕获到局部变量中:

def inside_local():
    local_x = x
    for a in xrange(n):
        if local_x:
            pass
        else:
            pass

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.258    0.258    0.258    0.258 testloop.py:13(outside)
    1    0.314    0.314    0.314    0.314 testloop.py:21(inside_local)
    1    0.421    0.421    0.421    0.421 testloop.py:6(inside)

大部分性能差异都消失了。

通常,只要有公共代码,就应该尝试封装它。如果if的分支除了循环之外没有任何共同点,那么尝试封装循环迭代器,例如:进入发电机。

答案 1 :(得分:5)

这就是我在这种情况下通常做的事情。

def inside():
    def x_true(a):
        pass

    def x_false(a):
        pass

    if x:
        fn = x_true
    else:
        fn = x_false

    for a in xrange(n):
        fn(a)

答案 2 :(得分:3)

python有像闭包,lambda函数,给函数的第一类状态和许多内置函数,这真的帮助我们删除重复的代码,例如想象你需要将函数应用于一系列值,你可以这样做吗

def outside():              
    if x:        # x is a flag or it could the function itself, or ...
        fun = sum # calc the sum, using pythons, sum function
    else:
        fun = lambda values: sum(values)/float(len(values)) # calc avg using our own function

    result = fun(xrange(101))

如果您给我们一个确切的场景,我们可以帮助您优化它。

答案 3 :(得分:2)

我知道在这个方向上没有提供支持的解释语言,编译语言很可能只进行一次比较(循环不变优化),但如果x的评估很简单,那就无济于事。 显然,代替pass语句的代码不能完全相同,因为“if”就没有用了。通常,人们会编写在两个地方调用的过程。

答案 4 :(得分:1)

def outside():
    def true_fn(a):
        pass
    def false_fn(a):
        pass

    fn = true_fn if x else false_fn
    for a in xrange(n):
        fn(a)

答案 5 :(得分:0)

在您的情况下,这取决于您的需求:可读性或性能。

如果您正在执行的任务是某种过滤器,您还可以使用list_comprehension来运行循环:

[e for e in xrange(n) if x]

如果您展示一些代码,我可以提出建议。

答案 6 :(得分:0)

根据您的原始问题,您希望在不花费大量系统资源的情况下测试x的值,您已经接受了将全局x的值复制到局部变量的答案。

现在,如果返回x的值涉及多步函数,但是你保证结果对于x总是相同的,那么我会考虑记忆该函数。 这是一个关于主题的非常好的stackoverflow link