Forma pitónica para evitar las declaraciones "if x: return x"

218

Tengo un método que llama a otros 4 métodos en secuencia para verificar condiciones específicas, y regresa de inmediato (sin verificar los siguientes) cada vez que uno devuelve algo Verdad.

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

Esto parece mucho código de equipaje. En lugar de cada instrucción if de 2 líneas, prefiero hacer algo como:

x and return x

Pero eso es inválido Python. ¿Me estoy perdiendo una solución simple y elegante aquí? Por cierto, en esta situación, esos cuatro métodos de verificación pueden ser costosos, por lo que no quiero llamarlos varias veces.

Bernardo
fuente
77
¿Qué son estas x? ¿Son solo Verdadero / Falso, o son estructuras de datos que contienen alguna información, con Ninguno o similar que se utiliza como un caso especial para indicar la ausencia de datos? Si es lo último, es casi seguro que debería usar excepciones en su lugar.
Nathaniel
13
@gerrit El código que se presenta arriba es hipotético / pseudocódigo que está fuera de tema en la Revisión de Código. Si el autor de la publicación desea que se revise su código de trabajo real y real , entonces sí, puede publicar en la Revisión del Código.
Phrancis
44
¿Por qué crees que x and return xes mejor que if x: return x? Este último es mucho más legible y, por lo tanto, mantenible. No debe preocuparse demasiado por la cantidad de caracteres o líneas; la legibilidad cuenta. De todos modos, son exactamente el mismo número de caracteres que no son espacios en blanco, y si realmente debe hacerlo, if x: return xfuncionará bien en una sola línea.
marcelm
3
Aclare si le importan los valores reales o si realmente solo necesita devolver un valor booleano. Esto hace una diferencia sobre las opciones disponibles y también cuáles comunican más claramente la intención. El nombramiento sugiere que solo necesita un booleano. También hace una diferencia si es importante evitar múltiples llamadas a estas funciones. También podría importar si las funciones toman alguno o diferentes conjuntos de parámetros. Sin estas aclaraciones, creo que esta pregunta se enmarca en No clara, Demasiado amplia o Basada en opiniones.
jpmc26
77
@ jpmc26 OP habla explícitamente de los valores de retorno de la verdad, y luego su código regresa x(en oposición a bool(x)), por lo que tal como está, creo que es seguro asumir que las funciones de OP pueden devolver cualquier cosa, y él quiere lo primero que sea verdadero.
timgeb

Respuestas:

278

Podrías usar un bucle:

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

Esto tiene la ventaja adicional de que ahora puede hacer que el número de condiciones sea variable.

Puede usar map()+ filter()(las versiones de Python 3, use las future_builtinsversiones de Python 2) para obtener el primer valor coincidente:

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)

pero si esto es más legible es discutible.

Otra opción es usar una expresión generadora:

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)
Martijn Pieters
fuente
27
si las condiciones son realmente solo condiciones, es decir, booleanos, en su primera propuesta también podría usar el builtin incorporado en anylugar del bucle. return any(condition() for condition in conditions)
44
@Leonhard: anytiene casi la misma implementación en su interior. Pero se ve mucho mejor,
publíquelo
13
La legibilidad triunfa sobre casi todas las demás consideraciones. Usted dice, el mapa / filtro es 'discutible', pongo mi voto por fea insoportablemente. Gracias, sin duda, pero si alguien en mi equipo coloca un mapa / filtro para este código, los transferiría a otro equipo o los asignaría al servicio de cama.
Kevin J. Rice
15
¿Es este bloque de código ilegible realmente "pitón"? ¿Y especialmente la idea de comprimir conditionsy arguments? Esto es en mi humilde opinión mucho peor que el código original, que toma alrededor de 10 segundos para analizar por mi analizador mental.
yo '
34
"Prefiero Python", dijeron. "Perl es ilegible", dijeron. Y entonces sucedió esto: return next((check for check in checks if check), None).
jja
393

Alternativamente a la buena respuesta de Martijn, podrías encadenar or. Esto devolverá el primer valor de verdad, o Nonesi no hay un valor de verdad:

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

Manifestación:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True
timgeb
fuente
99
Claro, pero esto será tedioso de leer rápido si hay más de unas pocas opciones. Además, mi enfoque le permite usar un número variable de condiciones.
Martijn Pieters
14
@MartijnPieters que puede usar \para poner cada cheque en su propia línea.
Caridorc
12
@MartijnPieters Nunca he dado a entender mi respuesta es mejor que la tuya, me gusta su respuesta, también :)
timgeb
38
@ Caridorc: No me gusta mucho usar \para extender la línea lógica. Use paréntesis donde sea posible; así que return (....)con líneas nuevas insertadas según sea necesario. Aún así, esa será una larga línea lógica.
Martijn Pieters
47
Creo que esta es la mejor solución. El argumento "se volverá tedioso [...] si hay más de unas pocas opciones" es discutible, porque de todos modos una sola función no debería estar haciendo un número exorbitante de controles. Si es necesario, las comprobaciones deben dividirse en múltiples funciones.
BlueRaja - Danny Pflughoeft
88

No lo cambies

Hay otras formas de hacerlo, como lo muestran las otras respuestas. Ninguno es tan claro como su código original.

Jack Aidley
fuente
39
Yo argumentaría en contra de eso, pero su sugerencia es legítima. Personalmente, encuentro mis ojos tensos tratando de leer el OP mientras, por ejemplo, la solución de timgeb hace clic instantáneamente.
Reti43
3
Es realmente una cuestión de opinión. Yo personalmente, eliminaría las nuevas líneas después :, porque considero if x: return xque está bastante bien, y hace que la función se vea más compacta. Pero eso puede ser solo yo.
yo '
2
No eres solo tú. Usar orcomo lo hizo timgeb es un idioma apropiado y bien entendido. Muchos idiomas tienen esto; tal vez cuando se le llama orelse, es aún más clara, pero incluso el viejo y simple or(o ||en otros idiomas) está destinado a ser entendida como la alternativa para probar si el primero "no funciona."
Ray Toal
1
@RayToal: Importar modismos desde otros idiomas es una excelente manera de ofuscar el código.
Jack Aidley
1
¡A veces sí, seguro! También puede ser una forma de abrir la mente y llevarlo a descubrir nuevos y mejores patrones y paradigmas que nadie puede haber intentado antes. El estilo evoluciona al tomar prestado y compartir y probar cosas nuevas. Funciona en ambos sentidos. De todos modos, nunca he escuchado el uso de oretiquetado como no pitónico o de ninguna manera ofuscado, pero de todos modos eso es una cuestión de opinión, como debería ser.
Ray Toal
83

Efectivamente, la misma respuesta que timgeb, pero podría usar paréntesis para un mejor formato:

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )
Wayne Werner
fuente
8
todos por favor ayuden a elevar esta respuesta al 1er lugar. Haz tu parte !
Gyom
74

De acuerdo con la ley de Curly , puede hacer que este código sea más legible dividiendo dos preocupaciones:

  • ¿Qué cosas reviso?
  • ¿Ha vuelto una cosa verdad?

en dos funciones:

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

Esto evita:

  • estructuras lógicas complicadas
  • líneas muy largas
  • repetición

... conservando un flujo lineal y fácil de leer.

Probablemente también pueda encontrar nombres de funciones aún mejores, de acuerdo con su circunstancia particular, que lo hacen aún más legible.

Phil Frost
fuente
Me gusta este, aunque Verdadero / Falso debe cambiarse a condición / Ninguno para que coincida con la pregunta.
Malcolm
2
¡Esta es mi favorita! También hace frente a diferentes controles y argumentos. Posiblemente sobrediseñado para este ejemplo en particular, ¡pero una herramienta realmente útil para futuros problemas!
rjh
44
Tenga en cuenta que return Noneno es necesario, porque las funciones regresan Nonepor defecto. Sin embargo, no hay nada de malo en regresar Noneexplícitamente, y me gusta que hayas elegido hacerlo.
timgeb
1
Creo que este enfoque se implementaría mejor con una definición de función local.
Jack Aidley
1
@timgeb "Explícito es mejor que implícito", Zen of Python .
jpmc26
42

Esta es una variante del primer ejemplo de Martijns. También utiliza el estilo "colección de llamadas" para permitir cortocircuitos.

En lugar de un bucle, puede usar el incorporado any.

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

Tenga en cuenta que anydevuelve un valor booleano, por lo que si necesita el valor de retorno exacto del cheque, esta solución no funcionará. anyno va a distinguir entre 14, 'red', 'sharp', 'spicy'como valores de retorno, todos ellos serán devueltos como True.


fuente
Podría hacer next(itertools.ifilter(None, (c() for c in conditions)))para obtener el valor real sin convertirlo en un valor booleano.
kojiro
1
¿ anyRealmente cortocircuito?
zwol
1
@zwol Sí, pruébelo con algunas funciones de muestra o consulte docs.python.org/3/library/functions.html
1
Esto es menos legible que encadenar las 4 funciones con 'o' y solo vale la pena si el número de condiciones es grande o dinámico.
rjh
1
@rjh Es perfectamente legible; es solo una lista literal y una comprensión. Lo preferiría porque mis ojos se llenan de lágrimas después de aproximadamente el tercerox = bar(); if x: return x;
Blacklight Shining
27

¿Has considerado simplemente escribir if x: return xtodo en una línea?

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

Esto no es menos repetitivo que lo que tenía, pero IMNSHO se lee bastante más suave.

zwol
fuente
24

Estoy bastante sorprendido de que nadie haya mencionado el incorporado anyque está hecho para este propósito:

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

Tenga en cuenta que aunque esta implementación es probablemente la más clara, evalúa todas las comprobaciones, incluso si la primera es True.


Si realmente necesita detenerse en la primera verificación fallida, considere usar reducewhich está hecho para convertir una lista a un valor simple:

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]): Aplica la función de dos argumentos acumulativamente a los elementos de iterable, de izquierda a derecha, para reducir el iterable a un solo valor. El argumento izquierdo, x, es el valor acumulado y el argumento derecho, y, es el valor de actualización del iterable. Si el inicializador opcional está presente, se coloca antes de los elementos del iterable en el cálculo

En tu caso:

  • lambda a, f: a or f()es la función que verifica si el acumulador ao la verificación actual f()es True. Tenga en cuenta que si aes así True, f()no será evaluado.
  • checkscontiene funciones de verificación (el felemento de la lambda)
  • False es el valor inicial, de lo contrario no se realizaría ninguna comprobación y el resultado siempre sería True

anyy reduceson herramientas básicas para la programación funcional. ¡Te recomiendo encarecidamente que los entrenes y que también mapsea ​​increíble!

ngasull
fuente
99
anysolo funciona si los cheques realmente devuelven un valor booleano, literalmente Trueo False, pero la pregunta no especifica eso. Debería usar reducepara devolver el valor real devuelto por el cheque. Además, es bastante fácil evitar evaluar todas las comprobaciones con anyun generador, por ejemplo any(c() for c in (check_size, check_color, check_tone, check_flavor)). Como en la respuesta de Leonhard
David Z
Me gusta su explicación y uso de reduce. Al igual que @DavidZ, creo que su solución anydebería usar un generador y debe señalarse que se limita a regresar Trueo False.
timgeb
1
@DavidZ realmente anyfunciona con valores verdaderos: any([1, "abc", False]) == Trueyany(["", 0]) == False
ngasull
3
@blint lo siento, no estaba claro. El objetivo de la pregunta es devolver el resultado de la verificación (y no simplemente indicar si la verificación tuvo éxito o no). Estaba señalando que anysolo funciona para ese propósito si los valores booleanos reales se devuelven de las funciones de verificación.
David Z
19

Si desea la misma estructura de código, ¡podría usar declaraciones ternarias!

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

Creo que esto se ve bien y claro si lo miras.

Manifestación:

Captura de pantalla de la ejecución

Phinet
fuente
77
¿Qué pasa con el pequeño pez ASCII sobre tu terminal?
36
@LegoStormtroopr Utilizo la concha de pescado, así que la adoro con una pecera ascii para hacerme feliz. :)
Phinet
3
Gracias por el buen pescado (y los colores por cierto, ¿qué editor es ese?)
mathreadler
44
Puede obtener peces en fishshell.com , y el archivo de configuración para el ascii aquí pastebin.com/yYVYvVeK , también el editor es texto sublime.
Phinet
99
x if x else <something>solo se puede reducir ax or <something>
5

Para mí, la mejor respuesta es la de @ phil-frost, seguida de @ wayne-werner.

Lo que me parece interesante es que nadie ha dicho nada sobre el hecho de que una función devolverá muchos tipos de datos diferentes, lo que hará que sea obligatorio hacer controles sobre el tipo de x para realizar cualquier trabajo adicional.

Entonces mezclaría la respuesta de @ PhilFrost con la idea de mantener 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

Observe que xse pasa como un argumento, pero también all_conditionsse usa como un generador pasado de funciones de verificación donde todas ellas obtienen un xcontrol, y devuelven Trueo False. Al usar funccon all_conditionsel valor predeterminado, puede usar assessed_x(x), o puede pasar un generador personalizado adicional a través de func.

De esa manera, obtendrá xtan pronto como pase un cheque, pero siempre será del mismo tipo.

juandesant
fuente
4

Idealmente, volvería a escribir las check_ funciones para devolver Trueo en Falselugar de un valor. Sus cheques luego se convierten

if check_size(x):
    return x
#etc

Suponiendo que su xno es inmutable, su función aún puede modificarlo (aunque no pueden reasignarlo), pero una función llamada checkrealmente no debería modificarlo de todos modos.

RoadieRich
fuente
3

Una ligera variación en el primer ejemplo de Martijns anterior, que evita el if dentro del bucle:

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status
mathreadler
fuente
¿Lo hace? Todavía haces una comparación. En su versión, también verificará todas las condiciones independientemente y no devolverá en la primera instancia de un valor verdadero, dependiendo de cuán costosas sean esas funciones, eso puede no ser deseable.
Reti43
44
@ Reti43: Status or c()omitirá / realizará un cortocircuito en las llamadas a c()if si Statuses verdad, por lo que el código en esta respuesta no parece llamar a más funciones que el código del OP. stackoverflow.com/questions/2580136/…
Neil Slater
2
@NeilSlater True. El único inconveniente que veo es que el mejor caso ahora está en O (n) porque el listiterator tiene que ceder n veces, cuando antes era O (1) si la primera función devuelve algo verdadero en O (1).
timgeb
1
Si buenos puntos. Solo tengo que esperar que c () tome un poco más de tiempo para evaluar que hacer un bucle casi vacío. Verificar el sabor podría tomar toda una noche al menos si es buena.
mathreadler
3

Me gusta @ timgeb's. Mientras tanto, me gustaría agregar que no es necesario expresar Noneen la returndeclaración ya orque se evalúa la colección de declaraciones separadas y se devuelve el primer cero, ninguno vacío, ninguno, y si no hay ninguno, Nonese devuelve si hay un Noneo no!

Entonces mi check_all_conditions()función se ve así:

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

Usando timeitcon number=10**7miré el tiempo de ejecución de una serie de sugerencias. En aras de la comparación, solo utilicé la random.random()función para devolver una cadena o en Nonefunción de números aleatorios. Aquí está el código completo:

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()

Y aquí están los resultados:

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

De esta manera está un poco fuera de la caja, pero creo que el resultado final es simple, legible y se ve bien.

La idea básica es raiseuna excepción cuando una de las funciones se evalúa como verdadera y devuelve el resultado. Así es como podría verse:

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

Necesitará una assertFalseyfunción que genere una excepción cuando uno de los argumentos de la función llamada se evalúe como verdadero:

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

Lo anterior podría modificarse para proporcionar también argumentos para las funciones que se evaluarán.

Y, por supuesto, necesitarás el TruthyExceptionmismo. Esta excepción proporciona el objectque desencadenó la excepción:

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

Puede convertir la función original en algo más general, por supuesto:

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)

Esto podría ser un poco más lento porque está utilizando una ifdeclaración y manejando una excepción. Sin embargo, la excepción sólo se maneja un máximo de una hora, por lo que el éxito de rendimiento debe ser menor a menos que vaya a ejecutar la verificación y obtener un Truevalor de muchos miles de veces.

Rick apoya a Monica
fuente
// , ¡Lindo! ¿Se considera "Pythonic" utilizar el manejo de excepciones para este tipo de cosas?
Nathan Basanese
@NathanBasanese Sure: se utilizan excepciones para controlar el flujo todo el tiempo. StopIterationes un buen ejemplo: se genera una excepción cada vez que se agota un iterable. Lo que desea evitar es aumentar sucesivamente las excepciones una y otra vez, lo que sería costoso. Pero hacerlo una vez no lo es.
Rick apoya a Monica el
//, Ah, supongo que te refieres a algo como programmers.stackexchange.com/questions/112463/… . He votado por esa pregunta y por esta respuesta. Los documentos de Python 3 para esto están aquí: docs.python.org/3/library/stdtypes.html#iterator-types , creo.
Nathan Basanese
1
¿Desea definir una función de propósito general y una excepción, solo para hacer algunas verificaciones en alguna otra función en algún lugar? Creo que eso es un poco demasiado.
Blacklight Shining
@BacklightShining Estoy de acuerdo. En realidad nunca haría esto yo mismo. El OP solicitó formas de evitar el código repetido, pero creo que con lo que comenzó está perfectamente bien.
Rick apoya a Monica el
2

La forma pitónica es usar reduce (como alguien ya mencionó) o itertools (como se muestra a continuación), pero me parece que simplemente usar un cortocircuito del oroperador produce un código más claro

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
Dmitry Rubanovich
fuente
0

Voy a saltar aquí y nunca he escrito una sola línea de Python, pero supongo que if x = check_something(): return xes válido.

si es así:

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
Richard87
fuente
1
No es válido Python, no. Python no te permite usar el operador de asignación de esa manera. Sin embargo, recientemente se agregó una nueva expresión de asignación especial, por lo que ahora puede escribir if ( x := check_size() ) :para el mismo efecto.
Jack Aidley
0

O use max:

def check_all_conditions():
    return max(check_size(), check_color(), check_tone(), check_flavor()) or None
U10-Adelante
fuente
-2

He visto algunas implementaciones interesantes de declaraciones de cambio / caso con dictos en el pasado que me llevaron a esta respuesta. Usando el ejemplo que ha proporcionado, obtendrá lo siguiente. (Es una locura using_complete_sentences_for_function_names, por check_all_conditionslo que cambia su nombre a status. Ver (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 función de selección elimina la necesidad de llamar cada check_FUNCTIONdos veces, es decir, evita check_FUNCTION() if check_FUNCTION() else nextagregar otra capa de función. Esto es útil para funciones de larga duración. Las lambdas en el dict retrasan la ejecución de sus valores hasta el ciclo while.

Como beneficio adicional, puede modificar el orden de ejecución e incluso omitir algunas de las pruebas alterando ky, spor ejemplo,k='c',s={'c':'b','b':None} reducir el número de pruebas e invertir el orden de procesamiento original.

Los timeitbecarios podrían regatear el costo de agregar una o dos capas adicionales a la pila y el costo de la búsqueda de datos, pero parece más preocupado por la belleza del código.

Alternativamente, una implementación más simple podría ser la siguiente:

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. Quiero decir esto no en términos de pep8 sino en términos de usar una palabra descriptiva concisa en lugar de una oración. De acuerdo, el OP puede estar siguiendo alguna convención de codificación, trabajando una base de código existente o no importa términos breves en su base de código.
Carel
fuente
1
A veces las personas se vuelven realmente locas cuando nombran una palabra. Usando el código del OP como ejemplo, es poco probable que tenga funciones llamadas check_no/some/even/prime/every_third/fancy_conditionspero solo esta función, entonces ¿por qué no llamarlo statuso si uno insiste check_status? Usar _all_es superfluo, no está asegurando la integridad de los universos. La nomenclatura seguramente debería usar un conjunto consistente de palabras clave que aprovechen el espaciado de nombres siempre que sea posible. Las oraciones largas sirven mejor como cadenas de documentos. Raramente se necesitan más de 8-10 caracteres para describir algo sucintamente.
Carel
1
Soy fanático de los nombres largos de funciones, porque quiero que las funciones de nivel superior sean autodocumentadas. Pero check_all_conditionses un mal nombre, porque es no comprobación de todas las condiciones, si una es verdadera. Yo usaría algo así matches_any_condition.
John Hazen
Ese es un tacto interesante para tomar. Trato de minimizar la cantidad de letras en las que escribiré errores tipográficos más tarde :) Parece que he incluido un montón de opiniones en mi solución, cuando realmente estaba tratando de proporcionar una pista útil. ¿Debería esto ser editado?
Carel
2
Esto parece demasiado hacky, especialmente teniendo en cuenta las otras soluciones en esta pregunta. Lo que OP intenta hacer no es complicado en absoluto; La solución debe ser lo suficientemente simple como para entender que está medio dormido. Y no tengo idea de lo que está pasando aquí.
Blacklight Shining
Estaba apuntando a la flexibilidad. Respuesta modificada para incluir una variante menos 'hacky'
Carel