8

Ho qualche argomento da discutere. Ho un frammento di codice con 24 ifs/elifs. L'operazione è la mia classe che rappresenta la funzionalità simile a Enum. Ecco un frammento di codice:Troppe dichiarazioni if ​​

if operation == Operation.START: 
    strategy = strategy_objects.StartObject() 
elif operation == Operation.STOP: 
    strategy = strategy_objects.StopObject() 
elif operation == Operation.STATUS: 
    strategy = strategy_objects.StatusObject() 
(...) 

Ho preoccupazioni dal punto di vista della leggibilità. È meglio cambiarlo in 24 classi e usare il polimorfismo? Non sono convinto che renderà il mio codice controllabile ... Da un lato i if sono abbastanza chiari e non dovrebbe essere difficile da seguire, d'altra parte ci sono troppi ifs.

La mia domanda è piuttosto generale, tuttavia sto scrivendo codice in python quindi non posso usare costruzioni come switch.

Cosa ne pensi?

UPDATE:

Una cosa importante è che StartObject(), StopObject() e StatusObject() sono costruttori e volevo assegnare un oggetto a strategia di riferimento.

+0

dichiarazione 'case', invece? oppure avere una serie di riferimenti al metodo per chiamare dinamicamente, ad es. qualunque sia l'equivalente python di 'array_of_methods [operation]()' sarebbe ... –

+3

Marc, python non ha istruzioni switch/case. https://www.python.org/dev/peps/pep-3103/ – Stiffo

risposta

12

È possibile utilizzare un dizionario. Dizionari riferimenti negozio, il che significa che le funzioni sono perfettamente valida da usare, in questo modo:.

operationFuncs = { 
    Operation.START: strategy_objects.StartObject 
    Operation.STOP: strategy_objects.StopObject 
    Operation.STATUS: strategy_objects.StatusObject 
    (...)     
} 

E 'bello avere un'operazione di default nel caso in cui, in modo che quando lo si esegue utilizzare un try except e gestire l'eccezione (vale a dire il equivalente del vostro else clausola)

try: 
    strategy = operationFuncs[operation]() 
except KeyError: 
    strategy = strategy_objects.DefaultObject() 

in alternativa utilizzare get il metodo di un dizionario, che consente di specificare un valore predefinito se la chiave che fornisci non è stata trovata.

strategy = operationFuncs.get(operation(), DefaultObject()) 

Si noti che non si include le parentesi quando memorizzandoli nel dizionario, basta usarli quando si chiama il dizionario. Anche questo richiede che Operation.START sia lavabile, ma dovrebbe essere il caso poiché lo hai descritto come una classe simile a ENUM.

0

Se il Operation.START, ecc sono hashable, è possibile utilizzare il dizionario con chiavi come la condizione ed i valori, come le funzioni di chiamata, ad esempio -

d = {Operation.START: strategy_objects.StartObject , 
    Operation.STOP: strategy_objects.StopObject, 
    Operation.STATUS: strategy_objects.StatusObject} 

E poi si può fare questo dizionario di ricerca e chiamare il funzione, Esempio -

d[operation]() 
3

L'equivalente di Python a un'istruzione switch è di utilizzare un dizionario. Essenzialmente è possibile memorizzare le chiavi come se fossero i casi e i valori sono quelli che verrebbero chiamati per quel caso particolare.Perché le funzioni sono oggetti in Python è possibile memorizzare quelli come i valori del dizionario:

operation_dispatcher = { 
    Operation.START: strategy_objects.StartObject, 
    Operation.STOP: strategy_objects.StopObject, 
} 

che possono poi essere utilizzati come segue:

try: 
    strategy = operation_dispatcher[operation] #fetch the strategy 
except KeyError: 
    strategy = default #this deals with the else-case (if you have one) 
strategy() #call if needed 

O più conciso:

strategy = operation_dispatcher.get(operation, default) 
strategy() #call if needed 

Questo può potenzialmente scala molto meglio che avere un casino di istruzioni if-else. Nota che se non hai un altro caso da trattare puoi semplicemente usare il dizionario direttamente con operation_dispatcher[operation].

-1

Ecco uno switch/caso imbastardito fatto usando dizionari:

Ad esempio:

# define the function blocks 
def start(): 
    strategy = strategy_objects.StartObject() 

def stop(): 
    strategy = strategy_objects.StopObject() 

def status(): 
    strategy = strategy_objects.StatusObject() 

# map the inputs to the function blocks 
options = {"start" : start, 
      "stop" : stop, 
      "status" : status, 

} 

Poi il blocco di commutazione equivalente viene richiamato:

options["string"]() 
+1

Ma 'strategy' sarà locale per ciascuna di queste piccole funzioni e non sarà visibile nell'ambito in cui si esegue la stringa' 'options [" "]()'. – Kevin

1

è possibile utilizzare alcuni introspezione con getattr:

strategy = getattr(strategy_objects, "%sObject" % operation.capitalize())() 

Diciamo che l'operazione è "STATO", sarà maiuscola come "Stato", quindi anteposta a "Oggetto", dando "StatusObject". Il metodo StatusObject verrà quindi chiamato su strategy_objects, in caso di errore irreversibile se questo attributo non esiste o se non è richiamabile. :) (Ad esempio, aggiungere la gestione degli errori.)

La soluzione dizionario è probabilmente più flessibile.

+0

Il controllo della classe esiste con hasattr (...) sarebbe l'equivalente di KeyError nella soluzione dizionario. –

2

Si potrebbe provare qualcosa come this.

Per esempio:

def chooseStrategy(op): 
    return { 
     Operation.START: strategy_objects.StartObject 
     Operation.STOP: strategy_objects.StopObject 
    }.get(op, strategy_objects.DefaultValue) 

chiamare in questo modo

strategy = chooseStrategy(operation)() 

Questo metodo ha il vantaggio di fornire un valore di default (come una dichiarazione finale altro). Naturalmente, se hai solo bisogno di utilizzare questa logica decisionale in un punto del tuo codice, puoi sempre usare strategy = dictionary.get (op, default) senza la funzione.

+0

Ti manca la chiamata. 'strategy = chooseStrategy (operation)()' – Brobin

+0

Grazie! Ho perso il fatto che stava chiamando la strategia scelta. Ho modificato il mio post. – weirdev