2010-03-29 11 views
9

Un amico (compagno di script di python ricreativo di basso livello) mi ha chiesto di controllare il codice. Ho notato che aveva 7 dichiarazioni separate che sostanzialmente dicevano.Python se l'efficienza dell'istruzione

if (a and b and c): 
    do something 

le dichiarazioni a, b, c hanno verificato la loro uguaglianza o la mancanza di valori impostati. Mentre lo guardavo, ho scoperto che a causa della natura dei test, potevo riscrivere l'intero blocco logico in 2 rami che non erano mai andati più di 3 in profondità e raramente superavano il primo livello (rendendo la prova più rara test fuori primo).

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

Per me, logicamente sembra che dovrebbe essere più veloce se si stanno facendo meno test, più semplici che non riescono più velocemente e andare avanti. Le mie vere domande sono

1) Quando dico se e se, se il caso fosse vero, il resto viene completamente ignorato?

2) In teoria sarebbe

se (a, b, c)

prendere tutto il tempo come i tre separato, se le dichiarazioni farebbe?

risposta

27

if le istruzioni salteranno tutto in una parentesi else se viene valutata su true. Va notato che preoccuparsi di questo tipo di problema, a meno che non venga eseguito milioni di volte per l'esecuzione del programma, è chiamato "ottimizzazione prematura" e dovrebbe essere evitato. Se il tuo codice è più chiaro con tre affermazioni if (a and b and c), dovrebbero essere lasciati in.

+11

concordato. Non preoccuparti delle prestazioni, preoccupati della leggibilità. Come dice Martin Fowler: "Ogni pazzo può scrivere codice che un computer può capire ... Ma solo i buoni programmatori scrivono un codice che gli umani possano capire". –

+2

E se sospetti di avere un problema di prestazioni, la prima cosa da fare è misurare la velocità del tuo codice. Quindi scrivi la tua versione alternativa, misurala e verifica se le tue modifiche rendono il codice più veloce. C'è una grande sezione in * Code Complete * su questo. –

1

if (a and b and c) non funzionerà se a è falsy e non si preoccupa di controllare b o c.

Detto questo, personalmente ritengo che i condizionali nidificati siano più facili da leggere rispetto alle 2^n combinazioni di condizionali.

In generale, se si desidera determinare quale metodo di esecuzione è più veloce, è possibile scrivere un benchmark semplice utilizzando timeit.

3

Dubito che potresti vedere una differenza misurabile, quindi ti consiglierei di fare tutto ciò che rende il codice più leggibile.

8

Almeno in python, l'efficienza è seconda alla leggibilità e "Flat è meglio di nidificata".

Vedi The Zen of Python

29

Direi che il singolo test è veloce come i test separati. Python fa anche uso del cosiddetto short-circuit evaluation.

Ciò significa che per (a and b and c), che b o c non sarebbero provati più se a è false.

simile, se si dispone di un OR espressione (a or b) e a è true, b non è mai valutato.

Quindi, per riassumere, le clausole non falliscono più rapidamente con separazione.

+0

Ottimo punto aggiuntivo, grazie. – Dennis

16

Codice:

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) 

uscita:

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   

Così, anche se l'impostazione è la stessa, la pulizia per l'espressione combinata è più veloce in quanto lascia un solo valore in pila.

3

Se siete preoccupati per le funzioni di B o C è che sono chiamati invece di solo variabili che vengono valutati, quindi questo codice mostra che cortocircuito è tuo amico:

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" 

stampe

if b was not called, then short-circuiting works 

Ma se si dispone di codice che fa questo:

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

allora il codice è stil Ho chiamato tutte e 3 le funzioni costose. Meglio lasciare Python be Python:

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

che chiamerà solo tante funzioni costose necessarie per determinare la condizione generale.

Modifica

Si può generalizzare questa operazione utilizzando il all built-in:

# 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 

Ora, se si deve aggiungere altre funzioni, o vogliono riordinarle (forse function_A è molto tempo- consumando, e si trarrebbe vantaggio dal filtraggio dei casi che non riescono function_B o function_C prima), basta aggiornare l'elenco funcs. all interrompe il cortocircuito proprio come se fosse stato specificato il numero if a and b and c. (Se le funzioni sono "or", utilizzare invece any.)

0

if (a and b and c) è più veloce e migliore, a fini di "ottimizzazione del codice Real Programmatore" e leggibilità del codice.

Problemi correlati