6

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

我想既然条件在循环内没有改变,我应该只在循环外测试一次,但是我将不得不“重复自己”并且可能不止一次地编写同一个循环。这是显示我的意思的代码:

#!/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>)

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

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

任何帮助表示赞赏,谢谢。

编辑:

实际上,在进行了一些测试之后,我现在确信性能差异主要受循环中执行的其他代码的影响,而不是受测试评估的影响。所以现在我坚持使用第一种形式,它更易读,更适合以后调试。

4

7 回答 7

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没有任何共同之处,那么尝试将循环迭代器封装到一个生成器中。

于 2012-06-20T08:49:46.033 回答
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)
于 2012-06-20T09:05:49.687 回答
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))

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

于 2012-06-20T09:36:08.380 回答
2

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

于 2012-06-20T08:49:37.380 回答
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)
于 2012-06-20T09:54:39.173 回答
0

在您的情况下,这取决于您想要什么:可读性或性能。

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

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

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

于 2012-06-20T08:41:25.020 回答
0

根据您最初的问题,您想在不花费大量系统资源的情况下测试 x 的值,您已经接受了一个涉及将全局 x 的值复制到局部变量的答案。

现在,如果返回 x 的值涉及多步函数,但您可以保证 x 的结果始终相同,那么我会考虑记忆该函数。这是关于该主题的非常好的stackoverflow链接

于 2012-06-20T18:44:46.260 回答