2010-03-29 37 views
9

Un amigo (compañero de scripts de python recreativo de bajo nivel de habilidad) me pidió que revisara algún código. Noté que tenía 7 declaraciones separadas que básicamente decían.Python if statement efficiency

if (a and b and c): 
    do something 

las declaraciones a, b, c probaron su igualdad o falta de valores establecidos. Cuando lo miré, descubrí que debido a la naturaleza de las pruebas, podía volver a escribir todo el bloque lógico en 2 ramas que nunca llegaban a más de 3 de profundidad y rara vez pasaban del primer nivel (lo que hacía que la prueba de ocurrencia más rara fuera primero).

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

Para mí, lógicamente, parece que debería ser más rápido si usted está haciendo menos pruebas más simples, que no logran más rápido y seguir adelante. Mis verdaderas preguntas son

1) Cuando digo if y else, si el if es cierto, ¿el resto se ignora por completo?

2) En teoría sería

si (a y b y c)

tomar tanto tiempo como los tres estados por separado si lo haría?

Respuesta

27

if declaraciones omitirán todo en un corchete else si se evalúa como verdadero. Cabe señalar que preocuparse por este tipo de problema, a menos que se realice millones de veces por ejecución del programa, se denomina "optimización prematura" y debe evitarse. Si su código es más claro con tres declaraciones if (a and b and c), deben dejarse en.

+11

De acuerdo. No se preocupe por el rendimiento, preocúpese por la legibilidad. Como Martin Fowler dice: "Cualquier tonto puede escribir código que una computadora puede entender ... Pero solo los buenos programadores escriben códigos que los humanos pueden entender". –

+2

Y si sospecha que tiene un problema de rendimiento, lo primero que debe hacer es medir la velocidad de su código. Luego, escriba su versión alternativa, mida eso y vea si su cambio hace que el código sea más rápido. Hay una gran sección en * Code Complete * sobre esto. –

1

if (a and b and c) fallarán si a es falso, y no se molestan en comprobar b o .

Dicho esto, personalmente siento que los condicionales anidados son más fáciles de leer que 2^n combinaciones de condicionales.

En general, si quiere determinar qué manera de hacer algo es más rápida, puede escribir un punto de referencia simple usando timeit.

3

Dudo que vea una diferencia mensurable, así que recomendaría hacer lo que sea que haga que el código sea más legible.

8

Al menos en python, la eficiencia es secundaria a la legibilidad y "Flat es mejor que anidado".

Ver The Zen of Python

29

Yo diría que la única prueba es tan rápido como las pruebas por separado. Python también hace uso de lo que se llama short-circuit evaluation.

Eso significa que por (a and b and c), que b o c no se probarían más si es afalse.

similares, si usted tiene una expresión OR(a or b) y a es true, b no se evalúa.

Resumiendo, las cláusulas no fallan más rápido con separación.

+0

Gran punto adicional, gracias. – Dennis

16

Código:

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) 

Salida:

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   

Por lo tanto, aunque la configuración es la misma, la limpieza de la expresión combinada es más rápido ya que deja un solo valor en la pila.

3

Si usted está preocupado acerca de las funciones de bienestar B o C que se llaman en lugar de sólo las variables que se evalúan, a continuación, este código indica que un cortocircuito es su amigo:

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" 

impresiones

if b was not called, then short-circuiting works 

Pero si tiene código que hace esto:

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... 

entonces su código es stil Llamar a las 3 funciones costosas. Es mejor dejar que sea Python Python:

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

que sólo llamar tantas funciones caros si es necesario para determinar el estado general.

Editar

Usted puede generalizar esto usando el all incorporada:

# 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 

Ahora bien, si hay que añadir otras funciones, o si desea cambiar su orden (tal vez function_A es muy tiempo- consumiendo, y se beneficiaría al filtrar los casos que fallan function_B o function_C primero), simplemente actualice la lista funcs. all hace un cortocircuito como si hubiera escrito el if como if a and b and c. (Si las funciones son 'OR siempre juntos, utilizar any orden interna en su lugar.)

0

if (a and b and c) es más rápido y mejor, por el bien de la 'optimización del código del Real programador' y la legibilidad del código.