Python-Syntax für "wenn a oder b oder c, aber nicht alle"


130

Ich habe ein Python-Skript, das entweder null oder drei Befehlszeilenargumente empfangen kann. (Entweder wird das Standardverhalten ausgeführt oder es müssen alle drei angegebenen Werte angegeben werden.)

Was ist die ideale Syntax für so etwas wie:

if a and (not b or not c) or b and (not a or not c) or c and (not b or not a):

?


4
Beginnen Sie vielleicht mit etwas wie `if len (sys.argv) == 0:
Edgar Aroutiounian

6
@EdgarAroutiounian len(sys.argv)wird immer mindestens 1 sein: Es enthält die ausführbare Datei als argv[0].
RoadieRich

10
Der Hauptteil der Frage stimmt nicht mit dem Titel der Frage überein. Möchten Sie überprüfen, ob a oder b oder c, aber nicht alle "oder" ob genau eines von a, b und c "(wie der von Ihnen angegebene Ausdruck)?
Doug McClean

2
Was können Sie über a + b + c sagen?
Gukoff

6
Warten Sie, Frage, es kann entweder null oder drei Argumente annehmen. if not (a and b and c)Könntest du nicht einfach sagen (null Argumente) und dann if a and b and c(alle drei Argumente)?
Akolyth

Antworten:


236

Wenn Sie eine minimale Form meinen, gehen Sie wie folgt vor:

if (not a or not b or not c) and (a or b or c):

Welches übersetzt den Titel Ihrer Frage.

UPDATE: Wie von Volatility und Supr richtig gesagt, können Sie das Gesetz von De Morgan anwenden und ein Äquivalent erhalten:

if (a or b or c) and not (a and b and c):

Mein Rat ist, die Form zu verwenden, die für Sie und andere Programmierer wichtiger ist. Das erste bedeutet "es gibt etwas Falsches, aber auch etwas Wahres" , das zweite "Es gibt etwas Wahres, aber nicht alles" . Wenn ich dies in der Hardware optimieren oder tun würde, würde ich die zweite wählen, hier nur die am besten lesbare auswählen (auch unter Berücksichtigung der Bedingungen, die Sie testen werden, und ihrer Namen). Ich habe den ersten ausgewählt.


3
Alles gute Antworten, aber dies gewinnt an Prägnanz mit großem Kurzschluss. Vielen Dank an alle!
Chris Wilson

38
Ich würde es noch prägnanter machen und mitif not (a and b and c) and (a or b or c)
Volatility

208
Oder sogar if (a or b or c) and not (a and b and c), um perfekt zum Titel zu passen;)
Supr

3
@HennyH Ich glaube, die Frage fragt nach "mindestens eine Bedingung wahr, aber nicht alle", nicht "nur eine Bedingung wahr".
Volatilität

63
@Suprif any([a,b,c]) and not all([a,b,c])
ewigmatt

238

Wie wäre es mit:

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

Andere Variante:

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

2
sum(conditions)kann schief gehen, wenn einer von ihnen 2zum Beispiel zurückkehrt, was ist True.
Eumiro

7
Wahre müßten Sie einen hässlichensum(map(bool, conditions))
jamylak

5
Beachten Sie, dass dies kein Kurzschluss ist, da alle Bedingungen vorab ausgewertet werden.
Georg

14
@PaulScheltema Das erste Formular ist für jeden verständlicher.
cmh

6
Dieses "alles und nicht alles" ist die beste und klarste der booleschen Methoden. Denken Sie nur an die wichtige Unterscheidung zwischen einem vorhandenen Argument und einem "wahrheitsgemäßen"
Argument

115

Diese Frage hatte bereits viele hoch bewertete Antworten und eine akzeptierte Antwort, aber alle waren bisher durch verschiedene Ausdrucksmöglichkeiten des booleschen Problems abgelenkt und haben einen entscheidenden Punkt übersehen:

Ich habe ein Python-Skript, das entweder null oder drei Befehlszeilenargumente empfangen kann. (Entweder wird das Standardverhalten ausgeführt oder es müssen alle drei angegebenen Werte angegeben werden.)

Diese Logik sollte nicht in erster Linie in der Verantwortung Ihres Codes liegen , sondern vomargparseModulbehandelt werden. Schreiben Sie keine komplexe if-Anweisung, sondern richten Sie Ihren Argument-Parser lieber wie folgt ein:

#!/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)

Und ja, es sollte eine Option sein, kein Positionsargument, denn es ist schließlich optional .


bearbeitet: Um das Problem von LarsH in den Kommentaren anzusprechen, finden Sie unten ein Beispiel dafür, wie Sie es schreiben könnten, wenn Sie sicher wären, dass Sie die Schnittstelle mit 3 oder 0 Positionsargumenten haben möchten. Ich bin der Meinung, dass die vorherige Benutzeroberfläche einen besseren Stil hat, da optionale Argumente Optionen sein sollten , aber der Vollständigkeit halber hier ein alternativer Ansatz. Beachten Sie das überschreibende kwargusagebeim Erstellen Ihres Parsers, daargparsesonst automatisch eine irreführende Verwendungsnachricht generiert wird!

#!/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)

Hier einige Anwendungsbeispiele:

# 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

4
Ja, das habe ich absichtlich hinzugefügt. Es wäre möglich, das Argument positionell zu machen und zu erzwingen, dass genau 3 oder 0 verbraucht werden, aber es würde keine gute CLI ergeben, daher habe ich es nicht empfohlen.
wim

8
Separate Ausgabe. Sie glauben nicht, dass es eine gute CLI ist, und Sie können für diesen Punkt argumentieren, und das OP kann überzeugt werden. Ihre Antwort weicht jedoch erheblich genug von der Frage ab, sodass die Änderung der Spezifikation erwähnt werden muss. Sie scheinen die Spezifikation so zu biegen, dass sie zum verfügbaren Werkzeug passt, ohne die Änderung zu erwähnen.
LarsH

2
@LarsH OK, ich habe ein Beispiel hinzugefügt, das besser zu der in der Frage implizierten ursprünglichen Schnittstelle passt. Jetzt wird das Werkzeug
gebogen

2
Dies ist die einzige Antwort, die ich positiv bewertet habe. +1 für die Beantwortung der eigentlichen Frage .
Jonathon Reinhart

1
+1. Die Form der CLI ist ein wichtiges tangentiales Thema, das nicht vollständig getrennt ist, wie eine andere Person sagte. Ich habe Ihren Beitrag ebenso wie andere positiv bewertet - Ihr Beitrag geht dem Problem auf den Grund und bietet eine elegante Lösung, während andere Beiträge die wörtliche Frage beantworten. Und beide Arten von Antworten sind nützlich und verdienen +1.
Ben Lee

32

Ich würde gehen für:

conds = iter([a, b, c])
if any(conds) and not any(conds):
    # okay...

Ich denke, dies sollte ziemlich effizient kurzschließen

Erläuterung

Wenn Sie condseinen Iterator erstellen, wird die erste Verwendung von anykurzgeschlossen und der Iterator zeigt auf das nächste Element, wenn ein Element wahr ist. Andernfalls wird die gesamte Liste verbraucht und sein False. Der nächste anynimmt die verbleibenden Elemente in der Iterable und stellt sicher, dass es keine anderen wahren Werte gibt ... Wenn ja, kann die gesamte Aussage nicht wahr sein, daher gibt es kein einziges Element (also Kurzschlüsse) nochmal). Der letzte anywird entweder zurückkehren Falseoder das iterable erschöpfen und sein True.

Hinweis: Die obige Prüfung prüft, ob nur eine einzige Bedingung festgelegt ist


Wenn Sie überprüfen möchten, ob ein oder mehrere Elemente, aber nicht jedes Element festgelegt ist, können Sie Folgendes verwenden:

not all(conds) and any(conds)

5
Ich verstehe es nicht Es lautet wie folgt: Wenn wahr und nicht wahr. Hilf mir, zu verstehen.
rGil

1
@rGil: Es lautet wie "wenn einige Äpfel rot sind und andere nicht" - es ist dasselbe wie zu sagen "einige Äpfel sind rot, aber nicht alle".
Georg

2
Selbst mit einer Erklärung kann ich das Verhalten nicht verstehen ... Mit [a, b, c] = [True, True, False]sollte Ihr Code nicht "gedruckt" werden False, während die erwartete Ausgabe erfolgt True?
Awesoon

6
Das ist ziemlich klug, ABER: Ich würde diesen Ansatz verwenden, wenn Sie nicht wüssten, wie viele Bedingungen Sie im Voraus hatten, aber für eine feste, bekannte Liste von Bedingungen lohnt sich der Verlust der Lesbarkeit einfach nicht.
flauschig

4
Dies schließt nicht kurz. Die Liste wird vollständig erstellt, bevor sie an übergeben wird iter. anyund allwird die Liste träge konsumieren, stimmt, aber die Liste wurde bereits vollständig ausgewertet, als Sie dort ankommen!
icktoofay

22

Der englische Satz:

"Wenn a oder b oder c, aber nicht alle"

Übersetzt in diese Logik:

(a or b or c) and not (a and b and c)

Das Wort "aber" impliziert normalerweise eine Konjunktion, mit anderen Worten "und". Darüber hinaus bedeutet "alle von ihnen" eine Verbindung von Bedingungen: diese Bedingung und diese Bedingung und andere Bedingung. Das "nicht" invertiert diese gesamte Konjunktion.

Ich bin nicht damit einverstanden, dass die Antwort akzeptiert. Der Autor hat es versäumt, die einfachste Interpretation auf die Spezifikation anzuwenden, und es versäumt, das De-Morgan-Gesetz anzuwenden, um den Ausdruck auf weniger Operatoren zu vereinfachen:

 not a or not b or not c  ->  not (a and b and c)

während behauptet wird, dass die Antwort eine "minimale Form" ist.


Eigentlich ist diese Form minimal. Es ist die minimale PoS-Form für den Ausdruck.
Stefano Sanfilippo

10

Dies wird zurückgegeben, Truewenn eine und nur eine der drei Bedingungen erfüllt ist True. Wahrscheinlich das, was Sie in Ihrem Beispielcode wollten.

if sum(1 for x in (a,b,c) if x) == 1:

Nicht so hübsch wie die Antwort von @defuz
jamylak

10

Was ist mit: (einzigartiger Zustand)

if (bool(a) + bool(b) + bool(c) == 1):

Beachten Sie, wenn Sie auch zwei Bedingungen zulassen, können Sie dies tun

if (bool(a) + bool(b) + bool(c) in [1,2]):

1
Für die Aufzeichnung fragt die Frage nach zwei Bedingungen. Mindestens einer, aber nicht alle = 1 von allen oder 2 von allen
Marius Balčytis

IMHO sollten Sie den zweiten als buchstabieren 1 <= bool(a) + bool(b) + bool(c) <= 2.
Stellen Sie Monica

6

Um klar zu sein, möchten Sie Ihre Entscheidung basierend darauf treffen, wie viele der Parameter logisch WAHR sind (im Fall von Zeichenfolgenargumenten - nicht leer)?

argsne = (1 if a else 0) + (1 if b else 0) + (1 if c else 0)

Dann haben Sie eine Entscheidung getroffen:

if ( 0 < argsne < 3 ):
 doSth() 

Jetzt ist die Logik klarer.


5

Und warum nicht einfach zählen?

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

Wenn es Ihnen nichts ausmacht, ein bisschen kryptisch zu sein, können Sie einfach rollen, mit 0 < (a + b + c) < 3dem Sie zurückkehrentrue wenn Sie zwischen einer und zwei wahre Aussagen haben, und falsch, wenn alle falsch sind oder keine falsch ist.

Dies vereinfacht sich auch, wenn Sie Funktionen zum Auswerten der Bools verwenden, da Sie die Variablen nur einmal auswerten. Dies bedeutet, dass Sie die Funktionen inline schreiben können und die Variablen nicht vorübergehend speichern müssen. (Beispiel : 0 < ( a(x) + b(x) + c(x) ) < 3.)


4

Die Frage besagt, dass Sie entweder alle drei Argumente (a und b und c) oder keines von ihnen (nicht (a oder b oder c)) benötigen.

Das gibt:

(a und b und c) oder nicht (a oder b oder c)


4

Soweit ich weiß, haben Sie eine Funktion, die 3 Argumente empfängt. Wenn dies nicht der Fall ist, wird sie mit dem Standardverhalten ausgeführt. Da Sie nicht erklärt haben, was passieren soll, wenn 1 oder 2 Argumente angegeben werden, gehe ich davon aus, dass es einfach das Standardverhalten ausführen sollte. In diesem Fall finde ich die folgende Antwort sehr vorteilhaft:

def method(a=None, b=None, c=None):
    if all([a, b, c]):
        # received 3 arguments
    else:
        # default behavior

Wenn Sie jedoch möchten, dass 1 oder 2 Argumente unterschiedlich behandelt werden:

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

Hinweis: Dies setzt voraus, dass " False" Werte nicht an diese Methode übergeben werden.


Die Überprüfung des Wahrheitswertes eines Arguments unterscheidet sich von der Überprüfung, ob ein Argument vorhanden ist oder nicht
wim

@wim Konvertiert also eine Frage, die Ihrer Antwort entspricht. Das Argparse-Modul hat nichts mit der Frage zu tun, es fügt einen weiteren Import hinzu, und wenn das OP überhaupt nicht plant, Argparse zu verwenden, hilft es ihnen überhaupt nicht. Wenn das "Skript" nicht eigenständig ist, sondern ein Modul oder eine Funktion in einem größeren Satz von Code, verfügt es möglicherweise bereits über einen Argumentparser, und diese bestimmte Funktion in diesem größeren Skript kann standardmäßig oder angepasst werden. Aufgrund der begrenzten Informationen aus dem OP kann ich nicht wissen, wie die Methode funktionieren soll, aber es ist sicher anzunehmen, dass das OP keine Bools passiert.
Inbar Rose

Die Frage lautete ausdrücklich "Ich habe ein Python-Skript, das entweder null oder drei Befehlszeilenargumente empfangen kann", nicht "Ich habe eine Funktion, die drei Argumente empfängt". Da das argparse-Modul die bevorzugte Methode zum Behandeln von Befehlszeilenargumenten in Python ist, hat es automatisch alles mit der Frage zu tun. Schließlich ist Python "Batterien enthalten" - es gibt keinen Nachteil beim "Hinzufügen eines weiteren Imports", wenn dieses Modul Teil der Standardbibliotheken ist.
wim

@wim Die Frage ist ziemlich unklar (Körper passt zum Beispiel nicht zum Titel). Ich denke, die Frage ist unklar genug, dass dies eine gültige Antwort für eine Interpretation ist.
Stellen Sie Monica

2

Wenn Sie mit einem Iterator von Bedingungen arbeiten, kann der Zugriff langsam sein. Sie müssen jedoch nicht mehr als einmal auf jedes Element zugreifen, und Sie müssen nicht immer alles lesen. Hier ist eine Lösung, die mit unendlichen Generatoren funktioniert:

#!/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])

0

Wenn jedes gegeben boolist Trueoder wenn jedes gegeben boolist False...
sie alle gleich!

Wir müssen also nur zwei Elemente finden, die unterschiedliche boolWerte ergeben, um
zu wissen, dass es mindestens eines Trueund mindestens eines gibt False.

Meine kurze Lösung:

not bool(a)==bool(b)==bool(c)

Ich glaube es schließt kurz, weil AFAIK a==b==cgleich ista==b and b==c .

Meine verallgemeinerte Lösung:

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)

Ich habe auch Code geschrieben, der sich mit mehreren Iterablen befasst, aber ich habe ihn von hier gelöscht, weil ich denke, dass er sinnlos ist. Es ist jedoch noch hier verfügbar .


-2

Dies ist im Grunde eine "einige (aber nicht alle)" Funktionalität (im Gegensatz zu any()undall() eingebauten Funktionen).

Dies impliziert, dass es Falses und True s unter den Ergebnissen geben sollte. Daher können Sie Folgendes tun:

some = lambda ii: frozenset(bool(i) for i in ii).issuperset((True, False))

# one way to test this is...
test = lambda iterable: (any(iterable) and (not all(iterable))) # see also http://stackoverflow.com/a/16522290/541412

# Some test cases...
assert(some(()) == False)       # all() is true, and any() is false
assert(some((False,)) == False) # any() is false
assert(some((True,)) == False)  # any() and all() are true

assert(some((False,False)) == False)
assert(some((True,True)) == False)
assert(some((True,False)) == True)
assert(some((False,True)) == True)

Ein Vorteil dieses Codes besteht darin, dass Sie die resultierenden (booleschen) Elemente nur einmal durchlaufen müssen.

Ein Nachteil ist, dass alle diese Wahrheitsausdrücke immer ausgewertet werden und nicht wie die / -Operatoren kurzschließen .orand


1
Ich denke, das ist unnötige Komplikation. Warum ein Frozenset anstelle eines einfachen alten Sets? Warum, .issupersetanstatt nur nach Länge 2 zu suchen, boolkann ich sowieso nichts anderes als Wahr und Falsch zurückgeben. Warum einem Namen ein Lambda (sprich: anonyme Funktion) zuweisen, anstatt nur ein Def zu verwenden?
wim

1
Die Lambda-Syntax ist für manche logischer. Sie sind sowieso gleich lang, da Sie sie benötigen, returnwenn Sie sie verwenden def. Ich finde die Allgemeinheit dieser Lösung gut. Es ist nicht notwendig, uns auf Boolesche Werte zu beschränken. Die Frage lautet im Wesentlichen: "Wie stelle ich sicher, dass alle diese Elemente in meiner Liste vorkommen?" Warum, setwenn Sie die Veränderlichkeit nicht brauchen? Mehr Unveränderlichkeit ist immer besser, wenn Sie die Leistung nicht benötigen.
Janus Troelsen

@ JanusTroelsen Du bist genau richtig! Dies sind einige Gründe, warum ich es so gemacht habe; es macht es mir einfacher und klarer. Ich neige dazu, Python an meine Codierungsweise anzupassen :-).
Abbafei

aber es wird nicht auf unendlichen Generatoren funktionieren: P siehe meine Antwort :) Hinweis:tee
Janus Troelsen

@ JanusTroelsen Mir ist klar :-). Eigentlich hatte ich es zuerst umgekehrt (mit True / False in der Menge und iterable in der Methode param), aber mir wurde klar, dass dies mit unendlichen Generatoren nicht funktionieren würde, und ein Benutzer könnte es nicht erkennen (da dies nicht der Fall ist) (noch) in den Dokumenten für iterable set method params erwähnt), und zumindest so ist es offensichtlich, dass es nicht unendlich viele Iteratoren braucht. Ich war mir bewusst, itertools.teeaber 1) ich suchte nach einem Einzeiler, der einfach / klein genug war, um das Einfügen von Kopien zu rechtfertigen, 2) Sie gaben bereits eine Antwort, die diese Technik verwendet :-)
Abbafei
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.