J'ai un script python qui peut recevoir zéro ou trois arguments de ligne de commande. (Soit il fonctionne sur le comportement par défaut ou nécessite les trois valeurs spécifiées.)
Quelle est la syntaxe idéale pour quelque chose comme:
if a and (not b or not c) or b and (not a or not c) or c and (not b or not a):
?
Si vous voulez dire une forme minimale, allez avec ceci:
if (not a or not b or not c) and (a or b or c):
Ce qui traduit le titre de votre question.
UPDATE: comme l'ont dit correctement Volatility et Supr, vous pouvez appliquer la loi de De Morgan et obtenir un équivalent:
if (a or b or c) and not (a and b and c):
Mon conseil est d'utiliser la forme la plus significative pour vous et pour les autres programmeurs. Le premier signifie "il y a quelque chose de faux, mais aussi de vrai" , le second "Il y a quelque chose de vrai, mais pas tout" . Si je devais optimiser ou faire cela dans le matériel, je choisirais le second, ici, choisissez le plus lisible (en tenant également compte des conditions que vous allez tester et de leurs noms). J'ai choisi le premier.
Que diriez-vous:
conditions = [a, b, c]
if any(conditions) and not all(conditions):
...
Autre variante:
if 1 <= sum(map(bool, conditions)) <= 2:
...
Cette question comportait déjà de nombreuses réponses très demandées et une réponse acceptée, mais toutes étaient jusqu'ici distraites par divers moyens d'exprimer le problème booléen et manquaient un point crucial:
J'ai un script python pouvant recevoir zéro ou trois commandes arguments de ligne. (Soit il fonctionne sur le comportement par défaut ou nécessite les trois valeurs Spécifiées)
Cette logique ne devrait pas être la responsabilité de votre code en premier lieu, elle devrait plutôt être gérée par le module argparse
. Ne vous embêtez pas pour écrire une instruction if si complexe, préférez plutôt configurer votre analyseur d'arguments comme ceci:
#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser()
parser.add_argument('--foo', nargs=3, default=['x', 'y', 'z'])
args = parser.parse_args()
print(args.foo)
Et oui, il devrait s'agir d'un option et non d'un argument de position, car c'est finalement optional.
édité:Pour répondre à la préoccupation de LarsH dans les commentaires, voici un exemple de la manière dont vous pourriez l'écrire si vous étiez certain de vouloir l'interface avec 3 ou 0 arguments positional. Je suis d’avis que l’interface précédente a un meilleur style, car les arguments optional devraient être options, mais voici une approche alternative par souci d’exhaustivité. Notez la valeur kwarg usage
lors de la création de votre analyseur, car argparse
générera automatiquement un message d'utilisation trompeur!
#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser(usage='%(prog)s [-h] [a b c]\n')
parser.add_argument('abc', nargs='*', help='specify 3 or 0 items', default=['x', 'y', 'z'])
args = parser.parse_args()
if len(args.abc) != 3:
parser.error('expected 3 arguments')
print(args.abc)
Voici quelques exemples d'utilisation:
# default case
wim@wim-zenbook:/tmp$ ./three_or_none.py
['x', 'y', 'z']
# explicit case
wim@wim-zenbook:/tmp$ ./three_or_none.py 1 2 3
['1', '2', '3']
# example failure mode
wim@wim-zenbook:/tmp$ ./three_or_none.py 1 2
usage: three_or_none.py [-h] [a b c]
three_or_none.py: error: expected 3 arguments
J'irais pour:
conds = iter([a, b, c])
if any(conds) and not any(conds):
# okay...
Je pense que cela devrait court-circuiter assez efficacement
Explication
En faisant de conds
un itérateur, la première utilisation de any
provoquera un court-circuit et laissera l'itérateur pointant sur l'élément suivant si l'un des éléments est vrai; sinon, il utilisera toute la liste et sera False
. La prochaine variable any
prend les éléments restants dans l'itérable et s'assure qu'il n'y a pas d'autres valeurs vraies ... S'il y en a, l'instruction entière ne peut pas être vraie, il n'y a donc pas un élément unique (si court circuits à nouveau). La dernière any
retournera False
ou épuisera la valeur itérable et sera True
.
note: ce qui précède vérifie si une seule condition est définie
Si vous souhaitez vérifier si un ou plusieurs éléments, mais pas tous les éléments sont définis, vous pouvez utiliser:
not all(conds) and any(conds)
La phrase anglaise:
“Si a ou b ou c mais pas tous”
Traduit à cette logique:
(a or b or c) and not (a and b and c)
Le mot "mais" implique généralement une conjonction, autrement dit "et". De plus, "tous" se traduit par une conjonction de conditions: cette condition, et cette condition, et autre. Le "non" inverse toute cette conjonction.
Je ne suis pas d'accord que la réponse acceptée. L'auteur a négligé d'appliquer l'interprétation la plus simple au cahier des charges et d'appliquer la loi de De Morgan pour simplifier l'expression à un nombre réduit d'opérateurs:
not a or not b or not c -> not (a and b and c)
en prétendant que la réponse est une "forme minimale".
Ceci retourne True
si une et une seule des trois conditions est True
. Probablement ce que vous vouliez dans votre exemple de code.
if sum(1 for x in (a,b,c) if x) == 1:
Qu'en est-il de: (condition unique)
if (bool(a) + bool(b) + bool(c) == 1):
Remarquez que si vous autorisez également deux conditions, vous pouvez le faire.
if (bool(a) + bool(b) + bool(c) in [1,2]):
Pour être clair, vous voulez prendre votre décision en fonction du nombre de paramètres qui sont logiques VRAI (dans le cas d’arguments de chaîne - pas vides)
argsne = (1 if a else 0) + (1 if b else 0) + (1 if c else 0)
Ensuite, vous avez pris une décision:
if ( 0 < argsne < 3 ):
doSth()
Maintenant la logique est plus claire.
Et pourquoi ne pas simplement les compter?
import sys
a = sys.argv
if len(a) = 1 :
# No arguments were given, the program name count as one
Elif len(a) = 4 :
# Three arguments were given
else :
# another amount of arguments was given
Si cela ne vous dérange pas d'être un peu cryptique, vous pouvez simplement lancer 0 < (a + b + c) < 3
qui retournera true
si vous avez entre une et deux instructions vraies et false si toutes sont fausses ou aucune n'est fausse.
Cela simplifie également l'utilisation de fonctions pour évaluer les bools, car vous n'évaluez les variables qu'une seule fois, ce qui signifie que vous pouvez écrire les fonctions en ligne sans avoir à stocker temporairement les variables. (Exemple: 0 < ( a(x) + b(x) + c(x) ) < 3
.)
Si je comprends bien, vous avez une fonction qui reçoit 3 arguments, mais si ce n’est pas le cas, elle fonctionnera sur le comportement par défaut. Puisque vous n'avez pas expliqué ce qui devrait arriver quand 1 ou 2 arguments sont fournis, je supposerai que cela devrait simplement faire le comportement par défaut. Dans ce cas, je pense que vous trouverez la réponse suivante très avantageuse:
def method(a=None, b=None, c=None):
if all([a, b, c]):
# received 3 arguments
else:
# default behavior
Toutefois, si vous souhaitez que 1 ou 2 arguments soient traités différemment:
def method(a=None, b=None, c=None):
args = [a, b, c]
if all(args):
# received 3 arguments
Elif not any(args):
# default behavior
else:
# some args (raise exception?)
note: Ceci suppose que "False
" ne sera pas passé dans cette méthode.
La question dit que vous avez besoin des trois arguments (a et b et c) ou de l'un d'entre eux (pas (a ou b ou c))
Cela donne:
(a et b et c) ou non (a ou b ou c)
Si vous travaillez avec un itérateur de conditions, l'accès peut être lent. Mais vous n'avez pas besoin d'accéder à chaque élément plus d'une fois, et vous n'avez pas toujours besoin de tout lire. Voici une solution qui fonctionnera avec des générateurs infinis:
#!/usr/bin/env python3
from random import randint
from itertools import tee
def generate_random():
while True:
yield bool(randint(0,1))
def any_but_not_all2(s): # elegant
t1, t2 = tee(s)
return False in t1 and True in t2 # could also use "not all(...) and any(...)"
def any_but_not_all(s): # simple
hadFalse = False
hadTrue = False
for i in s:
if i:
hadTrue = True
else:
hadFalse = True
if hadTrue and hadFalse:
return True
return False
r1, r2 = tee(generate_random())
assert any_but_not_all(r1)
assert any_but_not_all2(r2)
assert not any_but_not_all([True, True])
assert not any_but_not_all2([True, True])
assert not any_but_not_all([])
assert not any_but_not_all2([])
assert any_but_not_all([True, False])
assert any_but_not_all2([True, False])
Quand chaque donnée bool
est True
ou quand chaque donnée bool
est False
...
ils sont tous égaux!
Donc, nous avons juste besoin de trouver deux éléments qui donnent une valeur différente à bool
s
savoir qu’il existe au moins une True
et au moins une False
.
not bool(a)==bool(b)==bool(c)
Je crois que cela court-circuite, car AFAIK a==b==c
est égal à a==b and b==c
.
def _any_but_not_all(first, iterable): #doing dirty work
bool_first=bool(first)
for x in iterable:
if bool(x) is not bool_first:
return True
return False
def any_but_not_all(arg, *args): #takes any amount of args convertable to bool
return _any_but_not_all(arg, args)
def v_any_but_not_all(iterable): #takes iterable or iterator
iterator=iter(iterable)
return _any_but_not_all(next(iterator), iterator)
J'ai aussi écrit du code traitant de multiples itérables, mais je l'ai supprimé ici parce que je pense que c'est inutile. Il est cependant toujours disponible ici .