2010-03-29 104 views
9

一位朋友(同事低技能级别的娱乐python scripter)要求我查看一些代码。我注意到他有7个单独的陈述,基本上说。Python如果语句效率

if (a and b and c): 
    do something 

陈述a,b,c都测试了它们的相等或缺乏设定值。当我看到它时,我发现由于测试的本质,我可以将整个逻辑块重写为2个分支,这些分支的深度从未超过3个,并且很少超过第一级(使得最罕见的出现测试第一)。

if a: 
    if b: 
     if c: 
    else: 
     if c: 
else: 
    if b: 
     if c: 
    else: 
     if c: 

对我来说,在逻辑上好像应该是更快,如果你正在失败更快,移动就少,更简单的测试。 我的真正问题是

1)当我说if和else时,如果if为true,否则else会被完全忽略?

2)在理论上将

如果(a和b和c)

采取尽可能多的时间的三个单独的if语句会吗?

回答

27

if如果语句的计算结果为true,它将跳过else括号中的所有内容。应该指出的是,担心这类问题,除非每次程序执行都执行了数百万次,否则称为“过早优化”,应该避免。如果你的代码有三个if (a and b and c)陈述更清晰,他们应该留在

+11

同意。不要担心性能,担心可读性。就像Martin Fowler说的那样:“任何傻瓜都可以编写计算机可以理解的代码......但只有好的程序员才能编写人类可以理解的代码。” – 2010-03-29 17:21:14

+2

如果您怀疑自己有性能问题,首先要做的就是测量代码的速度。然后编写你的替代版本,测量它,并看看你的改变是否真的让代码更快。 * Code Complete *中有一个很棒的部分。 – 2010-03-29 17:22:03

1

if (a and b and c)将失败,如果a是虚假的,并且不麻烦检查bc。也就是说,我个人觉得嵌套条件比2^n个条件组合更容易阅读。一般来说,如果你想确定哪种方式做得最快,你可以使用timeit来编写一个简单的基准测试。

3

我怀疑你会看到一个可测量的差异,所以我建议做任何事情使代码最具可读性。

29

我会说一次测试是一样快的单独测试。 Python也使用所谓的short-circuit evaluation

这意味着对于(a and b and c),该bc将不能再进行测试,如果afalse

类似的,如果你有一个OR表达(a or b)atrueb不会求。

所以总结一下,子句不会更快分离。

+0

伟大的附加点,谢谢。 – Dennis 2010-03-29 19:48:34

16

代码:

import dis 

def foo(): 
    if (a and b and c): 
    pass 
    else: 
    pass 

def bar(): 
    if a: 
    if b: 
     if c: 
     pass 

print 'foo():' 
dis.dis(foo) 
print 'bar():' 
dis.dis(bar) 

输出:

foo(): 
    4   0 LOAD_GLOBAL    0 (a) 
       3 JUMP_IF_FALSE   18 (to 24) 
       6 POP_TOP    
       7 LOAD_GLOBAL    1 (b) 
      10 JUMP_IF_FALSE   11 (to 24) 
      13 POP_TOP    
      14 LOAD_GLOBAL    2 (c) 
      17 JUMP_IF_FALSE   4 (to 24) 
      20 POP_TOP    

    5   21 JUMP_FORWARD    1 (to 25) 
     >> 24 POP_TOP    

    7  >> 25 LOAD_CONST    0 (None) 
      28 RETURN_VALUE   
bar(): 
10   0 LOAD_GLOBAL    0 (a) 
       3 JUMP_IF_FALSE   26 (to 32) 
       6 POP_TOP    

11   7 LOAD_GLOBAL    1 (b) 
      10 JUMP_IF_FALSE   15 (to 28) 
      13 POP_TOP    

12   14 LOAD_GLOBAL    2 (c) 
      17 JUMP_IF_FALSE   4 (to 24) 
      20 POP_TOP    

13   21 JUMP_ABSOLUTE   29 
     >> 24 POP_TOP    
      25 JUMP_ABSOLUTE   33 
     >> 28 POP_TOP    
     >> 29 JUMP_FORWARD    1 (to 33) 
     >> 32 POP_TOP    
     >> 33 LOAD_CONST    0 (None) 
      36 RETURN_VALUE   

所以,虽然设置是相同的,对于联合表达的清理是更快,因为它仅留下在堆栈上的单个值。

3

如果你担心被调用,而不是仅仅被评估的变量B或C的存在功能,那么这段代码表明,短路是你的朋友:

a = False 
def b(): 
    print "b was called" 
    return True 

if a and b(): 
    print "this shouldn't happen" 
else: 
    print "if b was not called, then short-circuiting works" 

打印

if b was not called, then short-circuiting works 

但如果你有代码,这是否:

a = call_to_expensive_function_A() 
b = call_to_expensive_function_B() 
c = call_to_expensive_function_C() 

if a and b and c: 
    do something... 

那么你的代码是STIL l调用所有3个昂贵的函数。最好让Python成为Python:

if (call_to_expensive_function_A() and 
    call_to_expensive_function_B() and 
    call_to_expensive_function_C()) 
    do something... 

它只会调用尽可能多的昂贵的函数来确定整体条件。

# note, this is a list of the functions themselves 
# the functions are *not* called when creating this list 
funcs = [function_A, function_B, function_C] 

if all(fn() for fn in funcs): 
    do something 

现在,如果你需要添加其他功能,或者希望重新排序(也许function_A是非常耗时:

编辑

您可以在此使用all内置推广消耗,并且您将通过筛选首先失败function_Bfunction_C的案例受益),那么您只需更新funcs列表。 all确实发生短路,就好像你已经将if写成if a and b and c一样。 (如果功能一起使用,则使用any内建代替。)

0

if (a and b and c)为了实现“Real Programmer的代码优化”和代码可读性,速度越来越快。