Modifica di un numero elevato di istruzioni if-elif-else per utilizzare la struttura sottostante

3

Ho una funzione simile a questa:

function_name(step, ... , typ):
    if typ == 'some type of calc method':
         if step == 1:
             do_me_at_step_1(...)
         elif step == 2:
             do_me_at_step_2(...)
         elif ...
    elif typ == 'another calc method':
         if step == 1:
             do_me_at_step_1(...)
         elif ...

Speriamo che la struttura generale sia chiara qui. Quali sono le differenze tra i diversi typ s e come vengono gestiti? Esiste un'istanza di typ che cambia una variabile in un modo molto diverso da tutte le altre. Tutte le istanze di typ variano nel numero di istruzioni di passaggio separate, che vanno da if step == 1: ... else: a if step == 1 ... elif step == 5 ... else . Le ultime dichiarazioni elif e else sono diverse, in sostanza.

Come posso strutturare meglio questa funzione, perché al momento sembra orribile?

Il mio pensiero era di tirare fuori tutti i passaggi iniziali ... ma devo avere un modo per associare ogni typ con il passo che diventa "chiave". Così ho pensato in qualche modo sulla falsariga di:

function_name(step, ... , typ):
    orders = {typ1:key_step, typ2:key_step, ...}
    while key_step['typ1'] != step:
        if step == 1:
            ...
        step +=1

Ma ho notato due problemi: uno, questo non sfrutta appieno il ciclo while. Mi richiederebbe di aggiungere le istruzioni fino al massimo di tutti gli typ s che non sono "chiave", il che non mi sembra di sfruttare realmente le informazioni in orders .

L'altro problema è quando si è out del ciclo while. Suppongo che potresti fare qualcosa di simile

if typ == 'something':
    do_the_key_step_function_for_this_typ(...)

ma in realtà non sta facendo nulla di nuovo per ripulire questa funzione. Come posso cambiare la struttura di questo codice in modo che sia più pulito e più breve? C'è una struttura di base, quindi mi sento come se ci fosse un modo migliore per farlo.

Sentiti libero di chiedermi se hai bisogno di informazioni specifiche su questa funzione, ma spero che ci siano abbastanza dettagli forniti qui. Grazie!

    
posta heather 08.07.2018 - 01:40
fonte

3 risposte

4

La tua funzione iniziale esegue un passo specifico per un tipo specifico, entrambi forniti come argomento. Nel secondo caso si itera sui passaggi cercando di trovare quello giusto.

Alternativa 1: doppio dizionario

In base alla tua alternativa, ti propongo di prendere in considerazione un dizionario di dizionari, usando due indici invece di uno solo. Esempio:

# just some functions to be invoked
def do_me_at_step_1():
    return 1

def do_me_at_step_2():
    return 2

def do_other_step_2():
    return 102

# Use a dictionary assigning one type to a second level dictionary   
# In the second level dictionary, assign one step with a function 
command = { 'A': { 1:do_me_at_step_1, 2:do_me_at_step_2 }, 'B': { 1:do_me_at_step_1, 2:do_other_step_2 } }

# How you'd invoke the right function in function_name()
print (command['A'][1]())
print (command['A'][2]())
print (command['B'][1]())
print (command['B'][2]())

Quindi in questo caso, il tuo function_name () trova la funzione da invocare usando un dizionario di dizionari e poi li invoca.

Alternativa 2: codice orientato agli oggetti?

Potresti anche considerare l'utilizzo di un approccio orientato agli oggetti:

Quindi, in effetti, dovresti creare classi diverse, ognuna corrispondente a un tipo diverso. E poi invocherai per una istanziazione di una classe specifica il metodo corrispondente al passo.

In effetti, l'implementazione sarebbe molto vicina al pattern di stato

    
risposta data 08.07.2018 - 02:40
fonte
4

Questo è basato sulla risposta di Christophe .

La risposta di Christophe si concentra sull'uso di un doppio dizionario, con un set di chiavi typ e l'altro set di chiavi step . Tuttavia, questo non tiene conto dell'istruzione else - cioè, ad un certo punto, indipendentemente dal valore di step , viene utilizzata la stessa funzione, ma il dizionario non può saperlo.

In realtà c'è un modo abbastanza semplice per spiegare questo, ed è quello che sto usando in questo momento (anche se sarei molto aperto ad altre risposte che sembrano più pulite): usa un secondo dizionario con i casi predefiniti. Quindi la funzione diventa:

def function_name(step, ... , typ):
    command = { 'A': { 1:do_me_at_step_1, 2:do_me_at_step_2 }, 'B': { 1:do_me_at_step_1, 2:do_other_step_2 } }
    default_command = {'A':final_function_for_A, 'B':final_function_for_B, ...}
    try:
        return command[typ][step](inputs to function)
    except KeyError:
        return default_command[typ](inputs to function)

che è molto più bello di quello che ho avuto.

Modifica: poiché l'intercettazione di un errore è costosa, le ultime quattro righe probabilmente andrebbero meglio come if-else:

if step in command[typ].keyValues():
    return command[typ][step](...)
else:
    return default_command[typ](...)
    
risposta data 08.07.2018 - 04:06
fonte
1

Puoi chiamare le tue funzioni typ tra i passaggi comuni se passi la funzione come parametro, quindi chiamalo durante il tuo keytep.

# defining some dummy functions as the common code
def f1(*args):
    pass

def f2(*args):
    pass

def f3(*args):
    pass

# defining some functions for a specific 'typ'
def integer(*args):
    pass

def complex(*args):
    pass

# a dictionary that 1-indexes the common code
common_code = {
    1 : f1,
    2 : f2,
    3 : f3,
    # some lambdas in case your common code is simple or short
    4 : (lambda *args: args),
    5 : (lambda *args: args),
}

# the main function that lets us call a function for a chosen keystep
def calc_switch(step, calc_typ, *args):
    last = len(common_code)
    # perform the common steps from 1 until the chosen step
    # this for loop will not execute code if the user chooses 1
    for i in range(1, step):
        common_code[i](args)

    # the call to our step-dependent function
    calc_typ(args)

    # Continue calculating common code
    # if you want to exclude the step chosen by the user, then 
    # set the first argument of range to step+1
    for i in range(step, last + 1):
        common_code[i](args)


# some example calls, where the first argument is the key-step
calc_switch(2, complex)
calc_switch(4, integer)

Ora devi solo definire le funzioni del tuo passo chiave una volta e il tuo comportamento comune una volta. Ulteriori modifiche al codice comune verranno eseguite nel dizionario di codice comune.

Inoltre, se si desidera isolare la chiamata per un 'typ', è possibile utilizzare le funzioni parziali. Ad esempio:

from functools import partial

do_integer = partial(calc_switch, 2, complex)
do_complex = partial(calc_switch, 4, integer)

Ora avremo una funzione do_integer completa che esegue tutte le attività comuni, esegue il calcolo per typ 'intero' al passo chiave definito, esegue qualsiasi funzione comune dopo quel keystep e accetta qualsiasi argomento e passa alla funzione di base (tramite il parametro * args).

Spero che questo aiuti! Il codice è in Python 2.7, ma la roba del dizionario e le applicazioni parziali sono tutte in Python 3.

    
risposta data 08.07.2018 - 04:29
fonte

Leggi altre domande sui tag