Modo pitonico per evitare le istruzioni "if x: return x"


218

Ho un metodo che chiama in sequenza altri 4 metodi per verificare condizioni specifiche, e ritorna immediatamente (non controllando quelli seguenti) ogni volta che si restituisce qualcosa di vero.

def check_all_conditions():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

Sembra un sacco di codice bagaglio. Invece di ogni istruzione if a 2 righe, preferirei fare qualcosa del tipo:

x and return x

Ma questo non è Python valido. Mi sto perdendo una soluzione semplice ed elegante qui? Per inciso, in questa situazione, quei quattro metodi di controllo possono essere costosi, quindi non voglio chiamarli più volte.


7
Cosa sono queste x? Sono semplicemente Vero / Falso o sono strutture di dati contenenti alcune informazioni, con nessuna o simile utilizzata come caso speciale per indicare l'assenza di dati? Se è quest'ultimo, dovresti quasi certamente usare le eccezioni.
Nathaniel

13
@gerrit Il codice presentato sopra è un codice ipotetico / pseudo che è fuori tema su Code Review. Se l'autore del post desidera far rivedere il proprio codice di lavoro reale, effettivo , allora si può pubblicare un post su Code Review.
Phrancis,

4
Perché pensi che x and return xsia meglio di if x: return x? Quest'ultimo è molto più leggibile e quindi mantenibile. Non dovresti preoccuparti troppo del numero di caratteri o linee; la leggibilità conta. Sono comunque lo stesso numero esatto di caratteri non bianchi e, se proprio devi, if x: return xfunzioneranno bene su una sola riga.
marcelm

3
Si prega di chiarire se ci si preoccupa dei valori effettivi o è davvero necessario restituire un valore booleano. Ciò fa la differenza tra le opzioni disponibili e anche quali più chiaramente comunicano l'intento. La denominazione suggerisce che hai solo bisogno di un valore booleano. Fa anche la differenza se è importante evitare chiamate multiple a queste funzioni. Potrebbe anche importare se le funzioni accettano uno o più set di parametri diversi. Senza questi chiarimenti, penso che questa domanda rientri in uno poco chiaro, troppo ampio o basato sull'opinione.
jpmc26

7
@ jpmc26 OP parla esplicitamente di valori di ritorno veritieri, e quindi il suo codice ritorna x(al contrario bool(x)), così com'è, penso che sia sicuro presumere che le funzioni di OP possano restituire qualsiasi cosa, e vuole che il primo sia tutto vero.
timbro

Risposte:


278

È possibile utilizzare un ciclo:

conditions = (check_size, check_color, check_tone, check_flavor)
for condition in conditions:
    result = condition()
    if result:
        return result

Ciò ha l'ulteriore vantaggio di poter ora rendere variabile il numero di condizioni.

È possibile utilizzare map()+ filter()(le versioni di Python 3, utilizzare le future_builtinsversioni in Python 2) per ottenere il primo valore corrispondente:

try:
    # Python 2
    from future_builtins import map, filter
except ImportError:
    # Python 3
    pass

conditions = (check_size, check_color, check_tone, check_flavor)
return next(filter(None, map(lambda f: f(), conditions)), None)

ma se questo è più leggibile è discutibile.

Un'altra opzione è utilizzare un'espressione del generatore:

conditions = (check_size, check_color, check_tone, check_flavor)
checks = (condition() for condition in conditions)
return next((check for check in checks if check), None)

27
se le condizioni sono in realtà solo condizioni, vale a dire booleane, nella prima proposta è possibile utilizzare anche il comando incorporato anyanziché il ciclo. return any(condition() for condition in conditions)

4
@Leonhard: anyha quasi la stessa implementazione all'interno. Ma sembra molto meglio, per favore pubblicalo come risposta)
Nick Volynkin,

13
La leggibilità supera quasi tutte le altre considerazioni. Dici, la mappa / il filtro è "discutibile", ho espresso il mio voto per essere brutto. Grazie, certamente, ma se qualcuno nel mio team inserisse una mappa / filtro per questo codice, li trasferirei a un altro team o li assegnerei al servizio della padella.
Kevin J. Rice,

15
Questo blocco di codice illeggibile è davvero "pitone"? E soprattutto l'idea di zippare conditionse arguments? Questo è IMHO molto peggio del codice originale, che impiega circa 10 secondi per analizzare il mio brainparser.
yo '

34
"Preferisci Python", hanno detto. "Perl è illeggibile", hanno detto. E poi questo è accaduto: return next((check for check in checks if check), None).
jja

393

In alternativa alla bella risposta di Martijn, potresti incatenare or. Ciò restituirà il primo valore di verità, o Nonese non esiste alcun valore di verità:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor() or None

demo:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True

9
Certo, ma questo diventerà noioso da leggere velocemente se ci sono più di alcune opzioni. Inoltre il mio approccio ti consente di utilizzare un numero variabile di condizioni.
Martijn Pieters

14
@MartijnPieters è possibile utilizzare \per mettere ogni controllo sulla propria riga.
Caridorc,

12
@MartijnPieters Non ho mai sottinteso che la mia risposta sia migliore della tua, mi piace anche la tua risposta :)
timgeb

38
@Caridorc: non mi piace molto usare \per estendere la linea logica. Utilizzare invece le parentesi ove possibile; quindi return (....)con le nuove righe inserite se necessario. Comunque, quella sarà una lunga linea logica.
Martijn Pieters

47
Penso che questa sia la soluzione migliore. L'argomento "diventerà noioso [..] se ci sono più di alcune opzioni" è controverso, perché una singola funzione non dovrebbe comunque effettuare un numero esorbitante di controlli. Se necessario, i controlli dovrebbero essere suddivisi in più funzioni.
BlueRaja - Danny Pflughoeft,

88

Non cambiarlo

Esistono altri modi per farlo, come mostrano le varie altre risposte. Nessuno è chiaro come il tuo codice originale.


39
Direi contro questo, ma il tuo suggerimento è legittimo da esprimere. Personalmente, trovo gli occhi tesi a leggere l'OP mentre, ad esempio, la soluzione di Timgeb scatta all'istante.
Reti43,

3
È davvero una questione di opinione. Personalmente, rimuoverei le nuove righe dopo :, perché considero if x: return xpiuttosto bene, e rende la funzione più compatta. Ma potrei essere solo io.
yo '

2
Non sei solo tu. Usare orcome timgeb ha fatto un idioma corretto e ben compreso. Molte lingue hanno questo; forse quando viene chiamato orelseè ancora più evidente, ma anche semplice vecchia or(o ||in altre lingue) è destinato ad essere intesa come l'alternativa per provare se il primo "non funziona".
Ray Toal,

1
@RayToal: l'importazione di idiomi da altre lingue è un ottimo modo per offuscare il codice.
Jack Aidley,

1
A volte sì, certo! Inoltre può essere un modo per aprire la propria mente e indurlo a scoprire modelli e paradigmi nuovi e migliori che nessuno avrebbe mai provato prima. Lo stile si evolve da persone che prendono in prestito, condividono e provano cose nuove. Funziona in entrambi i modi. Ad ogni modo, non ho mai sentito l'uso di oretichettato non-Pythonic o in qualche modo offuscato, ma è comunque una questione di opinione --- come dovrebbe essere.
Ray Toal,

83

In effetti la stessa risposta di timgeb, ma è possibile utilizzare la parentesi per una formattazione migliore:

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )

8
tutti, per favore, aiutate ad aumentare questa risposta fino al 1 ° posto. fai la tua parte!
Gyom,

74

Secondo la legge di Curly , puoi rendere questo codice più leggibile dividendo due preoccupazioni:

  • Quali cose controllo?
  • Una cosa è tornata vera?

in due funzioni:

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions():
    for condition in all_conditions():
        if condition:
            return condition
    return None

Questo evita:

  • strutture logiche complicate
  • linee davvero lunghe
  • ripetizione

... preservando un flusso lineare e di facile lettura.

Probabilmente puoi anche trovare nomi di funzioni ancora migliori, in base alle tue circostanze particolari, che lo rendono ancora più leggibile.


Mi piace questo, anche se Vero / Falso dovrebbe essere modificato in condizione / Nessuno per soddisfare la domanda.
Malcolm,

2
Questo è il mio preferito! Affronta anche diversi controlli e argomenti. Molto probabilmente riprogettato per questo esempio particolare ma uno strumento davvero utile per problemi futuri!
RJ

4
Si noti che return Nonenon è necessario, poiché le funzioni ritornano Noneper impostazione predefinita. Tuttavia, non c'è niente di sbagliato nel tornare Noneesplicitamente, e mi piace che tu abbia scelto di farlo.
timgeb,

1
Penso che questo approccio sarebbe meglio implementato con una definizione di funzione locale.
Jack Aidley,

1
@timgeb "Esplicito è meglio che implicito", Zen of Python .
jpmc26

42

Questa è una variante del primo esempio di Martijns. Utilizza anche lo stile "raccolta di callable" per consentire il corto circuito.

Invece di un loop puoi usare il builtin any.

conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions) 

Nota che anyrestituisce un valore booleano, quindi se hai bisogno del valore restituito esatto del controllo, questa soluzione non funzionerà. anynon distinguere tra 14, 'red', 'sharp', 'spicy'come valori di ritorno, saranno tutti restituiti come True.


Potresti fare next(itertools.ifilter(None, (c() for c in conditions)))per ottenere il valore effettivo senza lanciarlo su un valore booleano.
Kojiro,

1
Fa anydavvero corto circuito?
zwol,

1
@zwol Sì, provalo con alcune funzioni di esempio o vedi docs.python.org/3/library/functions.html

1
Questo è meno leggibile che concatenare le 4 funzioni con 'o' e paga solo se il numero di condizioni è grande o dinamico.
RJ

1
@rjh È perfettamente leggibile; è solo un elenco letterale e una comprensione. Lo preferirei perché i miei occhi si velano dopo circa il terzox = bar(); if x: return x;
Blacklight Shining

27

Hai mai considerato di scrivere if x: return xtutto su una riga?

def check_all_conditions():
    x = check_size()
    if x: return x

    x = check_color()
    if x: return x

    x = check_tone()
    if x: return x

    x = check_flavor()
    if x: return x

    return None

Questo non è meno ripetitivo di quello che avevi, ma IMNSHO sembra un po 'più fluido.


24

Sono abbastanza sorpreso che nessuno abbia menzionato l'incasso anyche è stato realizzato per questo scopo:

def check_all_conditions():
    return any([
        check_size(),
        check_color(),
        check_tone(),
        check_flavor()
    ])

Si noti che sebbene questa implementazione sia probabilmente la più chiara, valuta tutti i controlli anche se lo è il primo True.


Se hai davvero bisogno di fermarti al primo controllo fallito, considera l'utilizzo di reducequale è fatto per convertire un elenco in un semplice valore:

def check_all_conditions():
    checks = [check_size, check_color, check_tone, check_flavor]
    return reduce(lambda a, f: a or f(), checks, False)

reduce(function, iterable[, initializer]): Applica cumulativamente la funzione di due argomenti agli elementi di iterabile, da sinistra a destra, in modo da ridurre l'iterabile a un singolo valore. L'argomento sinistro, x, è il valore accumulato e l'argomento destro, y, è il valore di aggiornamento dall'iterabile. Se l'inizializzatore opzionale è presente, viene inserito prima degli elementi dell'iterabile nel calcolo

Nel tuo caso:

  • lambda a, f: a or f()è la funzione che controlla che sia l'accumulatore ao il controllo corrente f()è True. Nota che, se lo aè True, f()non verrà valutato.
  • checkscontiene funzioni di controllo (l' felemento dalla lambda)
  • False è il valore iniziale, altrimenti non verrebbe eseguito alcun controllo e il risultato sarebbe sempre True

anye reducesono strumenti di base per la programmazione funzionale. Ti incoraggio vivamente ad allenarli e anche mapquesto è fantastico!


9
anyFunziona solo se i controlli restituiscono effettivamente un valore booleano, letteralmente Trueo False, ma la domanda non lo specifica. Dovresti utilizzare reduceper restituire il valore effettivo restituito dal controllo. Inoltre, è abbastanza facile evitare di valutare tutti i controlli anyutilizzando un generatore, ad es any(c() for c in (check_size, check_color, check_tone, check_flavor)). Come nella risposta di Leonhard
David Z,

Mi piace la tua spiegazione e utilizzo di reduce. Come @DavidZ, credo che la tua soluzione anydovrebbe usare un generatore e va sottolineato che è limitato alla restituzione Trueo False.
timbro

1
@DavidZ in realtà anyfunziona con valori sinceri: any([1, "abc", False]) == Trueeany(["", 0]) == False
ngasull il

3
@blint scusa, non ero chiaro. L'obiettivo della domanda è restituire il risultato del controllo (e non semplicemente indicare se il controllo ha avuto esito positivo o negativo). Stavo sottolineando che anyfunziona a tale scopo solo se i valori booleani effettivi vengono restituiti dalle funzioni di controllo.
David Z,

19

Se vuoi la stessa struttura di codice, puoi usare le dichiarazioni ternarie!

def check_all_conditions():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

Penso che questo sia carino e chiaro se lo guardi.

demo:

Schermata in esecuzione


7
Cosa succede con il piccolo pesce ASCII sopra il tuo terminale?

36
@LegoStormtroopr Uso il guscio di pesce, quindi lo decoro con un acquario ascii per farmi felice. :)
Phinet,

3
Grazie per l'ottimo pesce (e comunque i colori, che editor è quello?)
matematico

4
Puoi trovare pesce su fishshell.com e il file di configurazione per ascii qui pastebin.com/yYVYvVeK , anche l'editor è un testo sublime.
Phinet,

9
x if x else <something>può essere ridotto ax or <something>

5

Per me, la risposta migliore è quella di @ phil-frost, seguita da @ wayne-werner's.

Ciò che trovo interessante è che nessuno ha detto nulla sul fatto che una funzione restituirà molti tipi di dati diversi, il che renderà quindi obbligatorio fare controlli sul tipo di x stesso per fare qualsiasi ulteriore lavoro.

Quindi mescolerei la risposta di @ PhilFrost con l'idea di mantenere un solo tipo:

def all_conditions(x):
    yield check_size(x)
    yield check_color(x)
    yield check_tone(x)
    yield check_flavor(x)

def assessed_x(x,func=all_conditions):
    for condition in func(x):
        if condition:
            return x
    return None

Si noti che xviene passato come argomento, ma all_conditionsviene anche utilizzato come generatore passato di funzioni di controllo in cui tutte ottengono un oggetto xda controllare e restituire TrueoFalse . Usando funccon all_conditionscome valore predefinito, è possibile utilizzare assessed_x(x)o è possibile passare un ulteriore generatore personalizzato tramite func.

In questo modo, ricevi xnon appena passa un controllo, ma sarà sempre dello stesso tipo.


4

Idealmente, riscriverei le check_ funzioni per restituire Trueo Falsepiuttosto che un valore. I tuoi controlli diventano quindi

if check_size(x):
    return x
#etc

Supponendo che il tuo xnon sia immutabile, la tua funzione può comunque modificarlo (anche se non possono riassegnarlo) - ma una funzione chiamata checknon dovrebbe davvero modificarlo comunque.


3

Una leggera variazione sul primo esempio di Martijns sopra, che evita il if all'interno del loop:

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status

Vero? Fai ancora un confronto. Nella tua versione controllerai anche tutte le condizioni indipendentemente e non ritornerai alla prima istanza di un valore veritiero, a seconda di quanto costose sono quelle funzioni, che potrebbero non essere desiderabili.
Reti43,

4
@ Reti43: Status or c()salterà / cortocircuito valuterà le chiamate c()se Statusè vero, quindi il codice in questa risposta non sembra chiamare più funzioni del codice OP. stackoverflow.com/questions/2580136/…
Neil Slater

2
@NeilSlater True. L'unico aspetto negativo che vedo è che il caso migliore è ora in O (n) perché il listiterator deve cedere n volte, quando prima era O (1) se la prima funzione restituiva qualcosa di vero in O (1).
timgeb

1
Sì buoni punti. Devo solo sperare che c () impieghi un po 'più di tempo per valutare rispetto al loop di un loop quasi vuoto. Il controllo del sapore potrebbe richiedere un'intera serata almeno se è buono.
matematico

3

Mi piace @ timgeb's. Nel frattempo vorrei aggiungere che esprime Nonenella returndichiarazione non è necessario in quanto la raccolta delle ordichiarazioni separate vengono valutati e, nessuno-vuoto, viene restituito nessuno-None il primo nessuno-zero e se non c'è alcun allora Noneviene restituito se c'è Noneo no!

Quindi la mia check_all_conditions()funzione è simile a questa:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor()

Usando timeitcon number=10**7ho esaminato il tempo di esecuzione di una serie di suggerimenti. Per fare un confronto, ho appena usato la random.random()funzione per restituire una stringa o in Nonebase a numeri casuali. Ecco l'intero codice:

import random
import timeit

def check_size():
    if random.random() < 0.25: return "BIG"

def check_color():
    if random.random() < 0.25: return "RED"

def check_tone():
    if random.random() < 0.25: return "SOFT"

def check_flavor():
    if random.random() < 0.25: return "SWEET"

def check_all_conditions_Bernard():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

def check_all_Martijn_Pieters():
    conditions = (check_size, check_color, check_tone, check_flavor)
    for condition in conditions:
        result = condition()
        if result:
            return result

def check_all_conditions_timgeb():
    return check_size() or check_color() or check_tone() or check_flavor() or None

def check_all_conditions_Reza():
    return check_size() or check_color() or check_tone() or check_flavor()

def check_all_conditions_Phinet():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions_Phil_Frost():
    for condition in all_conditions():
        if condition:
            return condition

def main():
    num = 10000000
    random.seed(20)
    print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
    random.seed(20)
    print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
    random.seed(20)
    print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
    random.seed(20)
    print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
    random.seed(20)
    print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
    random.seed(20)
    print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))

if __name__ == '__main__':
    main()

E qui ci sono i risultati:

Bernard: 7.398444877040768
Martijn Pieters: 8.506569201346597
timgeb: 7.244275416364456
Reza: 6.982133448743038
Phinet: 7.925932800076634
Phil Frost: 11.924794811353031

2

In questo modo è un po 'fuori dagli schemi, ma penso che il risultato finale sia semplice, leggibile e bello.

L'idea di base è raiseun'eccezione quando una delle funzioni viene valutata come vera e restituisce il risultato. Ecco come potrebbe apparire:

def check_conditions():
    try:
        assertFalsey(
            check_size,
            check_color,
            check_tone,
            check_flavor)
    except TruthyException as e:
        return e.trigger
    else:
        return None

Avrai bisogno di una assertFalseyfunzione che solleva un'eccezione quando uno degli argomenti della funzione chiamata viene valutato come vero:

def assertFalsey(*funcs):
    for f in funcs:
        o = f()
        if o:
            raise TruthyException(o)

Quanto sopra potrebbe essere modificato in modo da fornire anche argomenti per le funzioni da valutare.

E ovviamente avrai bisogno dello TruthyExceptionstesso. Questa eccezione fornisce objectciò che ha attivato l'eccezione:

class TruthyException(Exception):
    def __init__(self, obj, *args):
        super().__init__(*args)
        self.trigger = obj

Naturalmente puoi trasformare la funzione originale in qualcosa di più generale:

def get_truthy_condition(*conditions):
    try:
        assertFalsey(*conditions)
    except TruthyException as e:
        return e.trigger
    else:
        return None

result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)

Questo potrebbe essere un po 'più lento perché stai usando sia ifun'istruzione che gestisci un'eccezione. Tuttavia, l'eccezione viene gestita al massimo una volta, quindi l'hit to performance dovrebbe essere minore a meno che non ti aspetti di eseguire il controllo e ottenere un Truevalore molte migliaia di volte.


// , Carina! È considerato "Pythonic" utilizzare la gestione delle eccezioni per questo genere di cose?
Nathan Basanese,

@NathanBasanese Sicuramente: le eccezioni vengono utilizzate per il flusso di controllo in ogni momento. StopIterationè un buon esempio: un'eccezione viene sollevata ogni volta che esaurisci un iterabile. La cosa che vuoi evitare è aumentare continuamente le eccezioni, il che diventerebbe costoso. Ma farlo una volta non lo è.
Rick supporta Monica il

//, Ah, presumo che ti riferisca a qualcosa come programmers.stackexchange.com/questions/112463/… . Ho votato per questa domanda e per questa risposta. I documenti di Python 3 per questo sono qui: docs.python.org/3/library/stdtypes.html#iterator-types , penso.
Nathan Basanese,

1
Vuoi definire una funzione per scopi generici e un'eccezione, solo per fare qualche controllo in qualche altra funzione da qualche parte? Penso che sia un po 'troppo.
Blacklight Shining

@BacklightShining Sono d'accordo. Non lo farei mai da solo. L'OP ha chiesto modi per evitare il codice ripetuto, ma penso che ciò con cui ha iniziato sia perfettamente perfetto.
Rick supporta Monica

2

Il modo pitonico sta usando il ridurre (come qualcuno già menzionato) o gli itertools (come mostrato sotto), ma mi sembra che il semplice utilizzo di un corto circuito ordell'operatore produca un codice più chiaro

from itertools import imap, dropwhile

def check_all_conditions():
    conditions = (check_size,\
        check_color,\
        check_tone,\
        check_flavor)
    results_gen = dropwhile(lambda x:not x, imap(lambda check:check(), conditions))
    try:
        return results_gen.next()
    except StopIteration:
        return None

0

Ho intenzione di saltare qui e non ho mai scritto una sola riga di Python, ma suppongo if x = check_something(): return xsia valido?

se è così:

def check_all_conditions():

    if (x := check_size()): return x
    if (x := check_color()): return x
    if (x := check_tone()): return x
    if (x := check_flavor()): return x

    return None

1
Non è valido Python, no. Python non ti consente di usare l'operatore di assegnazione in questo modo. Tuttavia, recentemente è stata aggiunta una nuova espressione di assegnazione speciale, quindi ora è possibile scrivere if ( x := check_size() ) :per lo stesso effetto.
Jack Aidley,

0

Oppure usa max:

def check_all_conditions():
    return max(check_size(), check_color(), check_tone(), check_flavor()) or None

-2

Ho visto alcune implementazioni interessanti di istruzioni switch / case con dicts in passato che mi hanno portato a questa risposta. Usando l'esempio che hai fornito otterrai quanto segue. (È una follia using_complete_sentences_for_function_names, quindi check_all_conditionsviene ribattezzato status. Vedi (1))

def status(k = 'a', s = {'a':'b','b':'c','c':'d','d':None}) :
  select = lambda next, test : test if test else next
  d = {'a': lambda : select(s['a'], check_size()  ),
       'b': lambda : select(s['b'], check_color() ),
       'c': lambda : select(s['c'], check_tone()  ),
       'd': lambda : select(s['d'], check_flavor())}
  while k in d : k = d[k]()
  return k

La funzione di selezione elimina la necessità di chiamare ciascuna check_FUNCTIONdue volte, ad esempio si evita check_FUNCTION() if check_FUNCTION() else nextaggiungendo un altro livello di funzione. Questo è utile per le funzioni di lunga durata. Gli lambda nel dict ritardano l'esecuzione dei suoi valori fino al ciclo while.

Come bonus, puoi modificare l'ordine di esecuzione e persino saltare alcuni dei test alterando ke, sad esempio, k='c',s={'c':'b','b':None}riduce il numero di test e inverte l'ordine di elaborazione originale.

Il timeit borsisti potrebbero contrattare sul costo dell'aggiunta di uno o due strati extra allo stack e il costo per il dict alzare lo sguardo, ma sembri più preoccupato della bellezza del codice.

In alternativa, un'implementazione più semplice potrebbe essere la seguente:

def status(k=check_size) :
  select = lambda next, test : test if test else next
  d = {check_size  : lambda : select(check_color,  check_size()  ),
       check_color : lambda : select(check_tone,   check_color() ),
       check_tone  : lambda : select(check_flavor, check_tone()  ),
       check_flavor: lambda : select(None,         check_flavor())}
  while k in d : k = d[k]()
  return k
  1. Intendo questo non in termini di pep8 ma in termini di utilizzo di una parola descrittiva concisa al posto di una frase. Concesso l'OP potrebbe seguire una convenzione di codifica, lavorare su una base di codice esistente o non occuparsi di termini concisi nella loro base di codice.

1
A volte le persone impazziscono con il loro nome quando una parola farà. Usando il codice del PO come esempio è improbabile che avrebbe chiamate funzioni check_no/some/even/prime/every_third/fancy_conditionsma solo questa funzione, quindi perché non chiamarlo statuso se si insiste check_status. L'uso _all_è superfluo, non sta garantendo l'integrità degli universi. La denominazione dovrebbe sicuramente utilizzare un insieme coerente di parole chiave che sfruttano la spaziatura dei nomi ogni volta che è possibile. Le frasi lunghe servono meglio come dotstrings. Raramente servono più di 8-10 caratteri per descrivere qualcosa in modo succinto.
Carel,

1
Sono un fan dei nomi di funzioni lunghe, perché voglio che le funzioni di livello superiore siano auto-documentanti. Ma check_all_conditionsè una cattiva fama, perché è non controllando tutte le condizioni se uno è vero. Userei qualcosa del genere matches_any_condition.
John Hazen,

Questo è un tatto interessante da prendere. Cerco di ridurre al minimo il numero di lettere che farò in seguito su un errore di battitura :) Sembra che abbia aggiunto un mucchio di opinioni alla mia soluzione, quando stavo davvero cercando di fornire un suggerimento utile. Questo dovrebbe essere modificato?
Carel,

2
Questo sembra troppo confuso, soprattutto considerando le altre soluzioni su questa domanda. Ciò che OP sta cercando di fare non è affatto complicato; la soluzione dovrebbe essere abbastanza semplice da capire il mezzo sonno. E non ho idea di cosa stia succedendo qui.
Blacklight Shining

Miravo alla flessibilità. Risposta modificata per includere una variante meno "confusa"
Carel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.