web-dev-qa-db-fra.com

Syntaxe Python pour "si a ou b ou c mais pas tous"

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

?

124
Chris Wilson

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.

223

Que diriez-vous:

conditions = [a, b, c]
if any(conditions) and not all(conditions):
   ...

Autre variante:

if 1 <= sum(map(bool, conditions)) <= 2:
   ...
231
defuz

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
112
wim

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)
33
Jon Clements

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". 

22
Kaz

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:
10
eumiro

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]):
9

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.

6
Danubian Sailor

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
5
Louis

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

5
Spinno

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.

4
Inbar Rose

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)

4

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])
2
Janus Troelsen

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 à bools
savoir qu’il existe au moins une True et au moins une False.

Ma solution courte:

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.

Ma solution généralisée:

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 .

0
GingerPlusPlus