Manière pythonique d'éviter les instructions «if x: return x»

218

J'ai une méthode qui appelle 4 autres méthodes dans l'ordre pour vérifier les conditions spécifiques et renvoie immédiatement (sans vérifier les suivantes) chaque fois que l'on retourne quelque chose de vrai.

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

Cela ressemble à beaucoup de code de bagages. Au lieu de chaque instruction if sur 2 lignes, je préfère faire quelque chose comme:

x and return x

Mais ce n'est pas Python invalide. Me manque-t-il une solution simple et élégante ici? Par ailleurs, dans cette situation, ces quatre méthodes de vérification peuvent être coûteuses, donc je ne veux pas les appeler plusieurs fois.

Bernard
la source
7
Quels sont ces x? S'agit-il simplement de Vrai / Faux, ou s'agit-il de structures de données contenant des informations, aucune ou similaire n'étant utilisée comme cas spécial pour indiquer l'absence de données? Si c'est le dernier, vous devriez presque certainement utiliser des exceptions à la place.
Nathaniel
13
@gerrit Le code tel que présenté ci-dessus est un code hypothétique / pseudo qui est hors sujet dans la révision de code. Si l'auteur de l'article souhaite que son code de travail réel et réel soit examiné, alors oui, il est le bienvenu pour publier sur Code Review.
Phrancis
4
Pourquoi pensez-vous que x and return xc'est mieux que if x: return x? Ce dernier est beaucoup plus lisible et donc maintenable. Vous ne devriez pas trop vous soucier du nombre de caractères ou de lignes; la lisibilité compte. Ce sont exactement le même nombre de caractères non blancs de toute façon, et si vous le devez vraiment, if x: return xcela fonctionnera bien sur une seule ligne.
marcelm
3
Veuillez préciser si vous vous souciez des valeurs réelles ou si vous avez juste besoin de renvoyer un booléen. Cela fait une différence quelles options sont disponibles et aussi lesquelles communiquent plus clairement l'intention. La dénomination suggère que vous n'avez besoin que d'un booléen. Il est également important de savoir s'il est important d'éviter plusieurs appels à ces fonctions. Il peut également être important que les fonctions prennent des ensembles de paramètres différents ou différents. Sans ces clarifications, je pense que cette question se situe dans l'une des questions peu claires, trop larges ou fondées sur des opinions.
jpmc26
7
@ jpmc26 OP parle explicitement de valeurs de retour véridiques, puis son code retourne x(par opposition à bool(x)) donc tel qu'il est, je pense qu'il est sûr de supposer que les fonctions de OP peuvent retourner n'importe quoi, et il veut le premier tout ce qui est véridique.
timgeb

Réponses:

278

Vous pouvez utiliser une boucle:

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

Cela a l'avantage supplémentaire que vous pouvez maintenant rendre le nombre de conditions variable.

Vous pouvez utiliser map()+ filter()(les versions de Python 3, utilisez les future_builtinsversions de Python 2) pour obtenir la première valeur correspondante:

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)

mais si c'est plus lisible, c'est discutable.

Une autre option consiste à utiliser une expression de générateur:

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
la source
27
si les conditions ne sont vraiment que des conditions, c'est-à-dire des booléens, alors dans votre première proposition, vous pouvez également utiliser le builtin anyau lieu de la boucle. return any(condition() for condition in conditions)
4
@Leonhard: anya presque la même implémentation à l'intérieur. Mais cela semble beaucoup mieux, veuillez le poster comme réponse)
Nick Volynkin
13
La lisibilité l'emporte sur presque toutes les autres considérations. Vous dites que la carte / le filtre est «discutable», je mets mon vote pour indiscutablement laid. Merci, certes, mais si quelqu'un de mon équipe met une carte / un filtre pour ce code, je les transfère à une autre équipe ou les affecte à des tâches de bassin de lit.
Kevin J. Rice
15
Ce bloc de code illisible est-il vraiment "pythonien"? Et surtout l'idée de zipper conditionset arguments? C'est à mon humble avis bien pire que le code d'origine, qui prend environ 10 secondes à analyser par mon analyseur de cerveau.
yo
34
"Préférez Python", ont-ils dit. "Perl est illisible", ont-ils déclaré. Et puis cela est arrivé: return next((check for check in checks if check), None).
jja
393

Alternativement à la bonne réponse de Martijn, vous pouvez enchaîner or. Cela retournera la première valeur véridique, ou Nones'il n'y a pas de valeur véridique:

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

Démo:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True
timgeb
la source
9
Bien sûr, mais cela deviendra fastidieux à lire rapidement s'il existe plusieurs options. De plus, mon approche vous permet d'utiliser un nombre variable de conditions.
Martijn Pieters
14
@MartijnPieters vous pouvez utiliser \pour placer chaque chèque sur sa propre ligne.
Caridorc
12
@MartijnPieters Je n'ai jamais laissé entendre que ma réponse est meilleure que la vôtre, j'aime aussi votre réponse :)
timgeb
38
@Caridorc: Je n'aime pas \du tout étendre la ligne logique. Utilisez plutôt des parenthèses si possible; donc return (....)avec des sauts de ligne insérés au besoin. Pourtant, ce sera une longue ligne logique.
Martijn Pieters
47
Je pense que c'est la meilleure solution. L'argument "cela deviendra fastidieux [...] s'il y a plus de quelques options" est sans objet, car une seule fonction ne devrait de toute façon pas faire un nombre exorbitant de vérifications. Si cela est nécessaire, les contrôles doivent être divisés en plusieurs fonctions.
BlueRaja - Danny Pflughoeft
88

Ne le change pas

Il existe d'autres façons de procéder, comme le montrent les diverses autres réponses. Aucun n'est aussi clair que votre code d'origine.

Jack Aidley
la source
39
Je m'y opposerais, mais votre suggestion est légitime à exprimer. Personnellement, je trouve mes yeux tendus en essayant de lire l'OP tandis que, par exemple, la solution de timgeb clique instantanément.
Reti43
3
C'est vraiment une question d'opinion. Personnellement, je supprimerais les sauts de ligne après :, car je considère que if x: return xc'est assez bien, et cela rend la fonction plus compacte. Mais c'est peut-être juste moi.
yo »
2
Ce n'est pas seulement toi. Utiliser orcomme timgeb l'a fait est un idiome approprié et bien compris. De nombreuses langues ont cela; peut - être quand on l'appelle , orelseil est encore plus clair, mais même simplement vieux or(ou ||dans d' autres langues) est destiné à être compris comme l'alternative à essayer si le premier « ne fonctionne pas. »
Ray Toal
1
@RayToal: L'importation d'idiomes à partir d'autres langues est un excellent moyen d'obscurcir le code.
Jack Aidley
1
Parfois oui, c'est sûr! Cela peut aussi être un moyen d'ouvrir son esprit et de le faire découvrir de nouveaux et meilleurs modèles et paradigmes que personne n'a peut-être essayés auparavant. Le style évolue en empruntant et en partageant et en essayant de nouvelles choses. Fonctionne dans les deux sens. Quoi qu'il en soit, je n'ai jamais entendu parler d' orétiquettes non pythoniques ou de quelque manière que ce soit obscurcies, mais c'est une question d'opinion de toute façon - comme il se doit.
Ray Toal
83

En fait, la même réponse que timgeb, mais vous pouvez utiliser des parenthèses pour un formatage plus agréable:

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )
Wayne Werner
la source
8
tout le monde aide à élever cette réponse jusqu'à la 1ère place. faites votre part!
Gyom
74

Selon la loi de Curly , vous pouvez rendre ce code plus lisible en divisant deux problèmes:

  • Quelles sont les choses que je vérifie?
  • Une chose est-elle revenue vraie?

en deux fonctions:

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

Cela évite:

  • structures logiques compliquées
  • très longues lignes
  • répétition

... tout en préservant un flux linéaire et facile à lire.

Vous pouvez probablement trouver des noms de fonctions encore meilleurs, selon votre situation particulière, ce qui les rend encore plus lisibles.

Phil Frost
la source
J'aime celui-ci, bien que True / False devrait être changé en condition / None pour correspondre à la question.
Malcolm
2
C'est mon préféré! Il gère également différents contrôles et arguments. Peut-être sur-conçu pour cet exemple particulier mais un outil vraiment utile pour les problèmes futurs!
rjh
4
Notez que ce return Nonen'est pas nécessaire, car les fonctions retournent Nonepar défaut. Cependant, il n'y a rien de mal à revenir Noneexplicitement, et j'aime que vous ayez choisi de le faire.
timgeb
1
Je pense que cette approche serait mieux mise en œuvre avec une définition de fonction locale.
Jack Aidley
1
@timgeb "Explicit vaut mieux qu'implicite", Zen of Python .
jpmc26
42

Ceci est une variante du premier exemple de Martijns. Il utilise également le style "collection de callables" afin de permettre les courts-circuits.

Au lieu d'une boucle, vous pouvez utiliser la fonction intégrée any.

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

Notez que anyrenvoie un booléen, donc si vous avez besoin de la valeur de retour exacte du chèque, cette solution ne fonctionnera pas. anyne distinguera pas 14, 'red', 'sharp', 'spicy'comme des valeurs de retour, ils seront tous retournés comme True.


la source
Vous pouvez faire next(itertools.ifilter(None, (c() for c in conditions)))pour obtenir la valeur réelle sans la convertir en valeur booléenne.
kojiro
1
Fait any- il réellement un court-circuit?
zwol
1
@zwol Oui, essayez-le avec quelques exemples de fonctions ou consultez docs.python.org/3/library/functions.html
1
Ceci est moins lisible que le chaînage des 4 fonctions avec 'ou' et ne rapporte que si le nombre de conditions est important ou dynamique.
rjh
1
@rjh C'est parfaitement lisible; c'est juste une liste littérale et une compréhension. Je préférerais ça parce que mes yeux brillent après environ le troisièmex = bar(); if x: return x;
Blacklight Shining
27

Avez-vous pensé à tout écrire if x: return xsur une seule ligne?

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

Ce n'est pas moins répétitif que ce que vous aviez, mais IMNSHO, il se lit un peu plus fluide.

zwol
la source
24

Je suis assez surpris que personne n'ait mentionné la fonction intégrée anyconçue à cet effet:

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

Notez que bien que cette implémentation soit probablement la plus claire, elle évalue toutes les vérifications même si la première l'est True.


Si vous devez vraiment vous arrêter au premier échec de vérification, envisagez d'utiliser reducece qui est fait pour convertir une liste en une valeur 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]): Appliquer la fonction de deux arguments cumulativement aux éléments de l'itérable, de gauche à droite, afin de réduire l'itérable à une seule valeur. L'argument de gauche, x, est la valeur cumulée et l'argument de droite, y, est la valeur de mise à jour de l'itérable. Si l'initialiseur optionnel est présent, il est placé avant les éléments de l'itérable dans le calcul

Dans ton cas:

  • lambda a, f: a or f()est la fonction qui vérifie que l'accumulateur aou la vérification actuelle f()est True. Notez que si aest True, f()ne sera pas évalué.
  • checkscontient des fonctions de vérification (l' félément du lambda)
  • False est la valeur initiale, sinon aucune vérification ne se produirait et le résultat serait toujours True

anyet reducesont des outils de base pour la programmation fonctionnelle. Je vous encourage fortement à les former ainsi que mapce qui est génial aussi!

ngasull
la source
9
anyne fonctionne que si les contrôles renvoient réellement une valeur booléenne, littéralement Trueou False, mais la question ne le spécifie pas. Vous devez utiliser reducepour renvoyer la valeur réelle renvoyée par le chèque. De plus, il est assez facile d'éviter d'évaluer toutes les vérifications avec anyen utilisant un générateur, par exemple any(c() for c in (check_size, check_color, check_tone, check_flavor)). Comme dans la réponse de Leonhard
David Z
J'aime votre explication et votre utilisation de reduce. Comme @DavidZ, je pense que votre solution anydevrait utiliser un générateur et il faut souligner qu'elle se limite au retour Trueou False.
timgeb
1
@DavidZ anyfonctionne réellement avec des valeurs véridiques: any([1, "abc", False]) == Trueetany(["", 0]) == False
ngasull
3
@blint désolé, je n'étais pas clair. L'objectif de la question est de renvoyer le résultat du contrôle (et non simplement d'indiquer si le contrôle a réussi ou échoué). Je faisais remarquer que cela anyne fonctionne à cette fin que si des valeurs booléennes réelles sont renvoyées par les fonctions de vérification.
David Z
19

Si vous voulez la même structure de code, vous pouvez utiliser des instructions ternaires!

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

Je pense que cela semble agréable et clair si vous le regardez.

Démo:

Capture d'écran de celui-ci en cours d'exécution

Phinet
la source
7
Qu'en est-il du petit poisson ASCII au-dessus de votre terminal?
36
@LegoStormtroopr J'utilise la coquille de poisson, donc je la décore avec un aquarium pour me faire plaisir. :)
Phinet
3
Merci pour le poisson fin (et les couleurs d'ailleurs, de quel éditeur s'agit-il?)
mathreadler
4
Vous pouvez obtenir du poisson sur fishshell.com , et le fichier de configuration pour l'ascii ici pastebin.com/yYVYvVeK , également l'éditeur est un texte sublime.
Phinet
9
x if x else <something>peut être réduit àx or <something>
5

Pour moi, la meilleure réponse est celle de @ phil-frost, suivie de @ wayne-werner.

Ce que je trouve intéressant, c'est que personne n'a dit quoi que ce soit sur le fait qu'une fonction retournera de nombreux types de données différents, ce qui rendra alors obligatoire la vérification du type de x lui-même pour effectuer d'autres travaux.

Je mélangerais donc la réponse de @ PhilFrost avec l'idée de garder un seul type:

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

Notez que cela xest passé comme argument, mais all_conditionsest également utilisé comme générateur passé de fonctions de vérification où toutes obtiennent un xà vérifier, et retournent Trueou False. En utilisant funcavec all_conditionscomme valeur par défaut, vous pouvez utiliser assessed_x(x)ou passer un autre générateur personnalisé via func.

De cette façon, vous obtenez xdès qu'un contrôle passe, mais ce sera toujours le même type.

juandesant
la source
4

Idéalement, je réécrirais les check_ fonctions à renvoyer Trueou Falseplutôt qu'une valeur. Vos chèques deviennent alors

if check_size(x):
    return x
#etc

En supposant que votre xn'est pas immuable, votre fonction peut toujours le modifier (bien qu'ils ne puissent pas le réaffecter) - mais une fonction appelée checkne devrait pas vraiment le modifier de toute façon.

RoadieRich
la source
3

Une légère variation sur le premier exemple de Martijns ci-dessus, qui évite le si à l'intérieur de la boucle:

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status
mathreadler
la source
Le fait-il? Vous faites toujours une comparaison. Dans votre version, vous vérifierez également toutes les conditions indépendamment et ne reviendrez pas à la première instance d'une valeur véridique, en fonction du coût de ces fonctions, ce qui peut ne pas être souhaitable.
Reti43
4
@ Reti43: Status or c()sautera / court-circui évaluera les appels à c()if Statusest véridique, donc le code dans cette réponse ne semble pas appeler plus de fonctions que le code de l'OP. stackoverflow.com/questions/2580136/…
Neil Slater
2
@NeilSlater True. Le seul inconvénient que je vois est que le meilleur cas est maintenant dans O (n) parce que le listitateur doit céder n fois, alors qu'il était O (1) avant si la première fonction renvoie quelque chose de vrai dans O (1).
timgeb
1
Oui de bons points. J'ai juste à espérer que le c () prend un peu plus de temps à évaluer que de boucler une boucle presque vide. Vérifier la saveur pourrait prendre une soirée entière au moins si elle est bonne.
mathreadler
3

J'aime @ timgeb. En attendant, je voudrais ajouter que l'expression Nonedans l' returninstruction n'est pas nécessaire car la collection d' orinstructions séparées est évaluée et la première non nulle, non vide, aucune-aucune est renvoyée et s'il n'y en a pas, elle Noneest renvoyée qu'il y en ait Noneou non!

Donc, ma check_all_conditions()fonction ressemble à ceci:

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

En utilisant timeitavec, number=10**7j'ai regardé le temps d'exécution d'un certain nombre de suggestions. Par souci de comparaison, je viens d'utiliser la random.random()fonction pour renvoyer une chaîne ou Nonebasée sur des nombres aléatoires. Voici tout le code:

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

Et voici les résultats:

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

Cette façon est un peu en dehors de la boîte, mais je pense que le résultat final est simple, lisible et joli.

L'idée de base est raiseune exception lorsque l'une des fonctions est évaluée comme véridique et renvoie le résultat. Voici à quoi cela pourrait ressembler:

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

Vous aurez besoin d'une assertFalseyfonction qui déclenche une exception lorsque l'un des arguments de la fonction appelée est évalué comme véridique:

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

Ce qui précède pourrait être modifié afin de fournir également des arguments pour les fonctions à évaluer.

Et bien sûr, vous aurez besoin de TruthyExceptionlui - même. Cette exception fournit le objectdéclencheur de l'exception:

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

Vous pouvez transformer la fonction d'origine en quelque chose de plus général, bien sûr:

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)

Cela peut être un peu plus lent car vous utilisez à la fois une ifinstruction et la gestion d'une exception. Cependant, l'exception n'est gérée qu'une seule fois au maximum, de sorte que l'atteinte aux performances doit être mineure, sauf si vous prévoyez d'exécuter la vérification et d'obtenir une Truevaleur plusieurs milliers de fois.

Rick soutient Monica
la source
// , Mignonne! Est-il considéré comme "Pythonic" d'utiliser la gestion des exceptions pour ce genre de chose?
Nathan Basanese
@NathanBasanese Bien sûr - les exceptions sont utilisées pour le contrôle du flux tout le temps. StopIterationest un assez bon exemple: une exception est déclenchée à chaque fois que vous épuisez un itérable. Ce que vous voulez éviter, c'est de soulever successivement des exceptions, ce qui coûterait cher. Mais ce n'est pas le cas une fois.
Rick soutient Monica
//, Ah, je suppose que vous faites référence à quelque chose comme programmers.stackexchange.com/questions/112463/… . J'ai voté pour cette question et pour cette réponse. Les documents Python 3 pour cela sont ici: docs.python.org/3/library/stdtypes.html#iterator-types , je pense.
Nathan Basanese
1
Vous voulez définir une fonction à usage général et une exception, juste pour faire quelques vérifications dans une autre fonction quelque part? Je pense que c'est un peu trop.
Blacklight Shining
@BacklightShining Je suis d'accord. Je ne ferais jamais ça moi-même. L'OP a demandé des moyens d'éviter le code répété, mais je pense que ce qu'il a commencé est parfaitement bien.
Rick soutient Monica
2

La méthode pythonique utilise soit réduire (comme quelqu'un a déjà mentionné) ou itertools (comme indiqué ci-dessous), mais il me semble que le simple fait de court-circuiter l' oropérateur produit un code plus clair

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
la source
0

Je vais sauter ici et je n'ai jamais écrit une seule ligne de Python, mais je suppose que if x = check_something(): return xc'est valide?

si c'est le cas:

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
la source
1
Ce n'est pas du Python valide, non. Python ne vous permet pas d'utiliser l'opérateur d'affectation comme ça. Cependant, une nouvelle expression d'affectation spéciale a été ajoutée très récemment, vous pouvez donc maintenant écrire if ( x := check_size() ) :pour le même effet.
Jack Aidley
0

Ou utilisez max:

def check_all_conditions():
    return max(check_size(), check_color(), check_tone(), check_flavor()) or None
U10-Forward
la source
-2

J'ai vu quelques implémentations intéressantes de déclarations switch / case avec dict dans le passé qui m'ont conduit à cette réponse. En utilisant l'exemple que vous avez fourni, vous obtiendrez ce qui suit. (C'est de la folie using_complete_sentences_for_function_names, c'est pourquoi il check_all_conditionsest renommé status. Voir (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 fonction de sélection élimine le besoin d'appeler chacun check_FUNCTIONdeux fois, c'est-à-dire que vous évitez check_FUNCTION() if check_FUNCTION() else nexten ajoutant une autre couche de fonction. Ceci est utile pour les fonctions de longue durée. Les lambdas dans le dict retardent l'exécution de ses valeurs jusqu'à la boucle while.

En bonus, vous pouvez modifier l'ordre d'exécution et même sauter certains tests en modifiant ket spar exemple en k='c',s={'c':'b','b':None}réduisant le nombre de tests et en inversant l'ordre de traitement d'origine.

Les timeitboursiers peuvent marchander sur le coût de l'ajout d'une couche supplémentaire ou deux à la pile et le coût de la recherche de dict, mais vous semblez plus préoccupé par la beauté du code.

Une implémentation plus simple peut également être la suivante:

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. Je veux dire cela non pas en termes de pep8 mais en termes d'utilisation d'un mot descriptif concis à la place d'une phrase. Certes, l'OP peut suivre une convention de codage, travailler sur une base de code existante ou ne pas prendre en compte les termes laconiques dans leur base de code.
Carel
la source
1
Parfois, les gens deviennent vraiment fous avec leur nom quand un mot fera l'affaire. En utilisant le code de l'OP comme exemple, il est peu probable qu'il ait des fonctions appelées check_no/some/even/prime/every_third/fancy_conditionsmais juste cette fonction, alors pourquoi ne pas l'appeler statusou si l'on insiste check_status. L'utilisation _all_est superflue, il ne garantit pas l'intégrité des univers. La dénomination doit sûrement utiliser un ensemble cohérent de mots-clés en utilisant autant que possible l'espacement des noms. Les phrases longues servent mieux de docstrings. On a rarement besoin de plus de 8 à 10 caractères pour décrire quelque chose succinctement.
Carel
1
Je suis un fan des noms de fonction longs, car je veux que les fonctions de niveau supérieur soient auto-documentées. Mais check_all_conditionsc'est un mauvais nom, car il ne vérifie pas toutes les conditions si l'une est vraie. J'utiliserais quelque chose comme matches_any_condition.
John Hazen
C'est un tact intéressant à prendre. J'essaie de minimiser le nombre de lettres sur lesquelles je ferai des fautes de frappe plus tard :) Il semble que j'aie pesé un tas d'opinions dans ma solution, alors que j'essayais vraiment de fournir un indice utile. Doit-il être édité?
Carel
2
Cela semble beaucoup trop hacky, surtout compte tenu des autres solutions sur cette question. Ce que OP essaie de faire n'est pas compliqué du tout; la solution doit être assez simple pour comprendre à moitié endormi. Et je n'ai aucune idée de ce qui se passe ici.
Blacklight Shining
Je visais la flexibilité. Réponse modifiée pour inclure une variante moins `` hacky ''
Carel