J'ai une méthode qui appelle 4 autres méthodes en séquence pour vérifier des conditions spécifiques, et retourne immédiatement (sans vérifier les suivantes) à chaque fois que l'on renvoie 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 déclaration de 2 lignes si, je préfère faire quelque chose comme:
x and return x
Mais ce n'est pas valide Python. Me manque-t-il une solution simple et élégante ici? Incidemment, dans cette situation, ces quatre méthodes de contrôle peuvent être coûteuses, je ne souhaite donc pas les appeler plusieurs fois.
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 présente l’avantage supplémentaire que vous pouvez maintenant modifier le nombre de conditions.
Vous pouvez utiliser map()
+ filter()
(les versions Python 3, utilisez les versions future_builtins
) dans 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 cela est plus lisible est discutable.
Une autre option consiste à utiliser une expression génératrice:
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)
Alternativement à la bonne réponse de Martijn, vous pouvez enchaîner or
. Cela retournera la première valeur de vérité, ou None
s'il n'y a pas de valeur de vérité:
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
ne le change pas
Comme le montrent les différentes réponses, il existe d'autres moyens de le faire. Aucun n'est aussi clair que votre code d'origine.
Dans la même réponse que timgeb, 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
)
Selon loi de Curly , vous pouvez rendre ce code plus lisible en scindant deux préoccupations:
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:
... tout en préservant un flux linéaire, facile à lire.
Vous pouvez probablement également proposer des noms de fonction encore meilleurs, en fonction de votre situation particulière, ce qui le rend encore plus lisible.
Ceci est une variante du premier exemple de Martijns. Il utilise également le style "collection of 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 any
renvoie un booléen. Si vous avez besoin de la valeur de retour exacte du contrôle, cette solution ne fonctionnera pas. any
ne fera pas la distinction entre 14
, 'red'
, 'sharp'
, 'spicy'
en tant que valeurs de retour, elles seront toutes renvoyées sous la forme True
.
Avez-vous pensé à écrire if x: return x
sur 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 le lit un peu plus facilement.
Je suis assez surpris que personne n'ait mentionné le --- any
qui est créé à 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 est True
.
Si vous avez vraiment besoin de vous arrêter à la première vérification, envisagez d'utiliser reduce
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])
: Applique la fonction de deux arguments de manière cumulative aux éléments d'iterable, de gauche à droite, afin de réduire l'iterable à une valeur unique. L'argument de gauche, x, est la valeur accumulée et l'argument de droite, y, 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 a
ou le contrôle en cours f()
est True
. Notez que si a
est True
, f()
ne sera pas évalué.checks
contient des fonctions de contrôle (l'élément f
du lambda)False
est la valeur initiale, sinon aucune vérification ne serait effectuée et le résultat serait toujours True
any
et reduce
sont des outils de base pour la programmation fonctionnelle. Je vous encourage fortement à les former aussi bien que map
qui est génial aussi!
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 a l'air bien et clair si vous le regardez.
Démo:
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 encore rien dit sur le fait qu’une fonction renvoie de nombreux types de données, ce qui obligera à vérifier le type de x lui-même pour effectuer des travaux supplémentaires.
Je mélangerais donc la réponse de @ PhilFrost avec l'idée de conserver 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 x
est passé en tant qu'argument, mais all_conditions
est également utilisé en tant que générateur passé de fonctions de vérification, où toutes doivent vérifier x
et renvoyer True
ou False
. En utilisant func
avec all_conditions
comme valeur par défaut, vous pouvez utiliser assessed_x(x)
, ou vous pouvez transmettre un autre générateur personnalisé via func
.
De cette façon, vous obtenez x
dès qu'un chèque est passé, mais ce sera toujours le même type.
Idéalement, je réécrirais les fonctions check_
pour renvoyer True
ou False
plutôt qu'une valeur. Vos chèques deviennent alors
if check_size(x):
return x
#etc
En supposant que votre x
ne soit pas immuable, votre fonction peut toujours la modifier (bien qu’elle ne puisse pas la réaffecter) - mais une fonction appelée check
ne devrait pas vraiment la modifier de toute façon.
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
La méthode Pythonic utilise soit la réduction (comme déjà mentionné), soit les outils (comme indiqué ci-dessous), mais il me semble que le simple fait de court-circuiter l'opérateur or
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
J'aime @ Timgeb's. Entre-temps, j'aimerais ajouter que l'expression de None
dans l'instruction return
n'est pas nécessaire, car la collection d'instructions séparées or
est évaluée et les premières valeurs non nul, non vide, none-None est retourné et s'il n'y en a pas, alors None
est renvoyé, qu'il y ait un None
ou pas!
Donc, ma fonction check_all_conditions()
ressemble à ceci:
def check_all_conditions():
return check_size() or check_color() or check_tone() or check_flavor()
Utilisation de timeit
avec number=10**7
J'ai examiné la durée d'exécution d'un certain nombre de suggestions. À des fins de comparaison, je viens d’utiliser la fonction random.random()
pour renvoyer une chaîne ou None
à partir de nombres aléatoires. Voici le code complet:
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 __== '__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
Cette façon est un peu en dehors de la boîte, mais je pense que le résultat final est simple, lisible et a l’air sympa.
L'idée de base est de raise
une exception lorsque l'une des fonctions est évaluée comme une vérité 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 fonction assertFalsey
qui déclenche une exception lorsque l'un des arguments de la fonction appelée est évalué comme une vérité:
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 la TruthyException
elle-même. Cette exception fournit la object
qui a déclenché l'exception:
class TruthyException(Exception):
def __init__(self, obj, *args):
super().__init__(*args)
self.trigger = obj
Vous pouvez bien sûr transformer la fonction d'origine en quelque chose de plus général:
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 instruction if
et une exception. Toutefois, l'exception étant gérée au maximum une fois, l'impact sur les performances doit être mineur, sauf si vous souhaitez exécuter le contrôle et obtenir une valeur True
plusieurs milliers de fois.
Je vais sauter ici et je n'ai jamais écrit une seule ligne de Python, mais je suppose que if x = check_something(): return x
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
Ou utilisez max
:
def check_all_conditions():
return max(check_size(), check_color(), check_tone(), check_flavor()) or None