Sintassi di Python per "if a o b o c ma non tutti"


130

Ho uno script Python che può ricevere zero o tre argomenti della riga di comando. (Funziona in base al comportamento predefinito o richiede tutti e tre i valori specificati.)

Qual è la sintassi ideale per qualcosa del genere:

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
forse iniziare con qualcosa come `if len (sys.argv) == 0:
Edgar Aroutiounian

6
@EdgarAroutiounian len(sys.argv)sarà sempre almeno 1: include l'eseguibile come argv[0].
RoadieRich,

10
Il corpo della domanda non corrisponde al titolo della domanda. Vuoi controllare "se a o b o c ma non tutti" o "se esattamente uno di a, b e c" (come fa l'espressione che hai dato)?
Doug McClean,

2
Cosa puoi dire di a + b + c?
Gukoff,

6
Aspetta, domanda, può richiedere zero o tre argomenti. non potresti semplicemente dire if not (a and b and c)(zero argomenti) e poi if a and b and c(tutti e tre i argomenti)?
accolito

Risposte:


236

Se intendi una forma minima, vai con questo:

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

Il che traduce il titolo della tua domanda.

AGGIORNAMENTO: come correttamente affermato da Volatility e Supr, è possibile applicare la legge di De Morgan e ottenere l'equivalente:

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

Il mio consiglio è di usare qualunque modulo sia più significativo per te e per gli altri programmatori. Il primo significa "c'è qualcosa di falso, ma anche qualcosa di vero" , il secondo "C'è qualcosa di vero, ma non tutto" . Se dovessi ottimizzare o fare questo in hardware, sceglierei il secondo, qui scelgo solo il più leggibile (tenendo anche conto delle condizioni che testerai e dei loro nomi). Ho scelto il primo.


3
Tutte ottime risposte, ma questo vince per concisione, con grandi cortocircuiti. Ringrazia tutti!
Chris Wilson,

38
Lo renderei ancora più conciso e andrei conif not (a and b and c) and (a or b or c)
Volatilità

208
O anche if (a or b or c) and not (a and b and c)per abbinare perfettamente il titolo;)
Supr

3
@HennyH Credo che la domanda richieda "almeno una condizione vera ma non tutte", non "solo una condizione vera".
Volatilità

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

238

Che ne dite di:

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

Altra variante:

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

2
sum(conditions)può andare storto se qualcuno di loro ritorna, 2ad esempio True.
eumiro,

7
È vero che avresti bisogno di un bruttosum(map(bool, conditions))
jamylak,

5
Si noti che questo non è un corto circuito, poiché tutte le condizioni sono pre-valutate.
Georg

14
@PaulScheltema Il primo modulo è più comprensibile per chiunque.
cmh

6
Questo "qualsiasi e non tutto" è il migliore e il più chiaro dei metodi booleani, basta essere consapevoli dell'importante distinzione tra un arg presente e un arg "verità"
wim

115

Questa domanda aveva già molte risposte molto apprezzate e una risposta accettata, ma finora tutte sono state distratte da vari modi di esprimere il problema booleano e hanno perso un punto cruciale:

Ho uno script Python che può ricevere zero o tre argomenti della riga di comando. (Funziona in base al comportamento predefinito o richiede tutti e tre i valori specificati)

Questa logica non dovrebbe essere la responsabilità del tuo codice in primo luogo , piuttosto dovrebbe essere gestito dalargparsemodulo. Non preoccuparti di scrivere un'istruzione if complessa, preferisci invece impostare il tuo parser di argomenti in questo modo:

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

E sì, dovrebbe essere un'opzione non un argomento posizionale, perché dopo tutto è facoltativo .


modificato: per rispondere alle preoccupazioni di LarsH nei commenti, di seguito è riportato un esempio di come potresti scriverlo se fossi certo di volere l'interfaccia con 3 o 0argomenti posizionali . Sono dell'opinione che l'interfaccia precedente abbia uno stile migliore, perché gliargomenti opzionali dovrebbero essere opzioni , ma ecco un approccio alternativo per completezza. Nota la sostituzione di kwargusagedurante la creazione del tuo parser, perchéargparsealtrimenti genererà automaticamente un messaggio di utilizzo fuorviante!

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

Ecco alcuni esempi di utilizzo:

# 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
Sì, l'ho aggiunto intenzionalmente. Sarebbe possibile rendere l'argomento posizionale e imporre che vengano consumati esattamente 3 o 0, ma non sarebbe una buona CLI, quindi non l'ho raccomandato.
mercoledì

8
Problema separato. Non credi che sia una buona CLI e puoi discutere per quel punto e il PO potrebbe essere persuaso. Ma la tua risposta si discosta dalla domanda in modo abbastanza significativo da menzionare il cambiamento delle specifiche. Sembra che tu stia piegando le specifiche per adattarle allo strumento disponibile, senza menzionare la modifica.
LarsH,

2
@LarsH OK, ho aggiunto un esempio che si adatta meglio all'interfaccia originale implicita nella domanda. Ora sta piegando lo strumento per soddisfare le specifiche disponibili ...;)
wim

2
Questa è l'unica risposta che ho votato. +1 per aver risposto alla vera domanda .
Jonathon Reinhart,

1
+1. La forma della CLI è un'importante questione tangenziale, non completamente separata come ha detto un'altra persona. Ho votato il tuo post così come altri: il tuo è alla radice del problema e offre una soluzione elegante, mentre altri post rispondono alla domanda letterale. E entrambi i tipi di risposte sono utili e meritano +1.
Ben Lee,

32

Vorrei:

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

Penso che questo dovrebbe cortocircuitare in modo abbastanza efficiente

Spiegazione

Creando condsun iteratore, il primo utilizzo di anycortocircuita e lascia l'iteratore che punta all'elemento successivo se un oggetto è vero; in caso contrario, consumerà l'intero elenco e sarà False. Il prossimo anyprende gli elementi rimanenti nell'iterabile e si assicura che non ci siano altri valori veri ... Se ci sono, l'intera istruzione non può essere vera, quindi non c'è un elemento unico (quindi i cortocircuiti ancora). L'ultimo anytornerà Falseo esaurirà l'iterabile e sarà True.

nota: quanto sopra controlla se è impostata una sola condizione


Se vuoi verificare se uno o più articoli, ma non tutti gli articoli sono impostati, puoi usare:

not all(conds) and any(conds)

5
Non capisco Si legge come: se vero e non vero. Aiutami a capire
rGil

1
@rGil: recita "se alcune mele sono rosse e altre no" - equivale a dire "alcune mele sono rosse, ma non tutte".
georg

2
Anche con una spiegazione non riesco a capire il comportamento ... Con il [a, b, c] = [True, True, False]tuo codice non dovrebbe "stampare" False, mentre l'output previsto è True?
1313

6
Questo è piuttosto intelligente, MA: userei questo approccio se non sapessi quante condizioni hai avuto in anticipo, ma per un elenco fisso e noto di condizionali, la perdita di leggibilità semplicemente non ne vale la pena.
soffice

4
Questo non fa corto circuito. L'elenco è completamente costruito prima di essere passato a iter. anye allconsumerò pigramente l'elenco, vero, ma l'elenco è già stato completamente valutato al momento del tuo arrivo!
icktoofay

22

La frase inglese:

"Se a o b o c ma non tutti"

Si traduce in questa logica:

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

La parola "ma" di solito implica una congiunzione, in altre parole "e". Inoltre, "tutti loro" si traduce in una congiunzione di condizioni: questa condizione, e quella condizione, e altre condizioni. Il "non" inverte l'intera congiunzione.

Non sono d'accordo che la risposta accettata. L'autore ha trascurato di applicare l'interpretazione più semplice alla specifica e ha trascurato di applicare la legge di De Morgan per semplificare l'espressione a un minor numero di operatori:

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

sostenendo che la risposta è una "forma minima".


In realtà, quella forma è minima. È la forma PoS minima per l'espressione.
Stefano Sanfilippo,

10

Questo ritorna Truese una e solo una delle tre condizioni è True. Probabilmente quello che volevi nel tuo codice di esempio.

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

Non carina come la risposta di @defuz
jamylak,

10

Che dire: (condizione unica)

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

Nota, se consenti anche due condizioni, potresti farlo

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

1
Per la cronaca, la domanda richiede due condizioni. Almeno uno, ma non tutti = 1 di tutti o 2 di tutti
Marius Balčytis,

IMHO dovresti scrivere il secondo come 1 <= bool(a) + bool(b) + bool(c) <= 2.
Ripristina Monica

6

Per essere chiari, vuoi prendere la tua decisione in base a quanti parametri sono VERO logici (in caso di argomenti stringa - non vuoti)?

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

Quindi hai preso una decisione:

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

Ora la logica è più chiara.


5

E perché non contarli?

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

Se non ti dispiace essere un po 'enigmatico puoi semplicemente rotolare con il 0 < (a + b + c) < 3quale tornerà truese hai tra una e due affermazioni vere e false se tutte sono false o nessuna è falsa.

Ciò semplifica anche se si utilizzano le funzioni per valutare i bool mentre si valutano le variabili una sola volta e ciò significa che è possibile scrivere le funzioni in linea e non è necessario archiviare temporaneamente le variabili. (Esempio:. 0 < ( a(x) + b(x) + c(x) ) < 3)


4

La domanda afferma che sono necessari tutti e tre gli argomenti (a e b e c) o nessuno di essi (non (a o b o c))

Questo da:

(a e b e c) o no (a o b o c)


4

A quanto ho capito, hai una funzione che riceve 3 argomenti, ma in caso contrario verrà eseguito sul comportamento predefinito. Dal momento che non hai spiegato cosa dovrebbe accadere quando vengono forniti 1 o 2 argomenti, suppongo che dovrebbe semplicemente fare il comportamento predefinito. In tal caso, penso che troverai la seguente risposta molto vantaggiosa:

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

Tuttavia, se si desidera che 1 o 2 argomenti vengano gestiti in modo diverso:

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

nota: questo presuppone che i Falsevalori " " non verranno passati in questo metodo.


controllare il valore di verità di un argomento è una questione diversa dal verificare se un argomento è presente o assente
wim

@wim Quindi sta convertendo una domanda per soddisfare la tua risposta. Il modulo argparse non ha nulla a che fare con la domanda, aggiunge un'altra importazione e se l'OP non ha intenzione di usare affatto argparse, non li aiuterà in alcun modo. Inoltre, se lo "script" non è autonomo, ma un modulo o una funzione all'interno di un set di codice più grande, potrebbe già avere un parser di argomenti e questa particolare funzione all'interno di quello script più grande può essere predefinita o personalizzata. A causa delle informazioni limitate fornite dall'OP, non posso sapere come dovrebbe funzionare il metodo, ma è sicuro supporre che l'OP non stia passando bool.
Inbar Rose,

La domanda ha detto esplicitamente "Ho uno script Python che può ricevere zero o tre argomenti della riga di comando", non ha detto "Ho una funzione che riceve 3 argomenti". Poiché il modulo argparse è il modo preferito di gestire gli argomenti della riga di comando in Python, ha automaticamente tutto a che fare con la domanda. Infine, python è "batterie incluse" - non c'è alcun aspetto negativo con "l'aggiunta di un'altra importazione" quando quel modulo fa parte delle librerie standard.
mercoledì

@wim La domanda non è chiara (il corpo non corrisponde al titolo, per esempio). Penso che la domanda non sia abbastanza chiara da essere una risposta valida per una sua interpretazione.
Ripristina Monica

2

Se lavori con un iteratore di condizioni, potrebbe essere lento accedervi. Ma non è necessario accedere a ciascun elemento più di una volta e non è sempre necessario leggerlo tutto. Ecco una soluzione che funzionerà con infiniti generatori:

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

Quando ogni dato boolè True, o quando ogni dato boolè False...
sono tutti uguali tra loro!

Quindi, dobbiamo solo trovare due elementi che valutino le diverse bools
per sapere che ce n'è almeno uno Truee almeno uno False.

La mia breve soluzione:

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

Lo credo cortocircuiti, perché AFAIK è a==b==cugualea==b and b==c .

La mia soluzione generalizzata:

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)

Ho anche scritto del codice relativo a più iterabili, ma l'ho eliminato da qui perché penso sia inutile. È comunque ancora disponibile qui .


-2

Questa è sostanzialmente una funzionalità "alcune (ma non tutte)" (se confrontata con any()eall() funzioni incorporate ).

Ciò implica che non ci dovrebbe essere Falses e True s tra i risultati. Pertanto, è possibile effettuare le seguenti operazioni:

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)

Un vantaggio di questo codice è che devi iterare una sola volta attraverso gli elementi (booleani) risultanti.

Uno svantaggio è che tutte queste espressioni di verità vengono sempre valutate e non fanno cortocircuiti come gli operatori or/ and.


1
Penso che questa sia una complicazione inutile. Perché un frozenset invece di un semplice vecchio set? Perché .issupersetinvece di controllare solo la lunghezza 2, boolnon può restituire nient'altro che Vero e Falso comunque. Perché assegnare un lambda (leggi: funzione anonima) a un nome invece di usare semplicemente un def?
mercoledì

1
la sintassi lambda è più logica per alcuni. hanno comunque la stessa lunghezza poiché è necessario returnse si utilizza def. penso che la generalità di questa soluzione sia buona. non è necessario limitarci ai booleani, la domanda è essenzialmente "come posso garantire che tutti questi elementi si presentino nel mio elenco". perché setse non hai bisogno della mutabilità? più immutabilità è sempre meglio se non hai bisogno delle prestazioni.
Janus Troelsen,

@JanusTroelsen Hai ragione! Questi sono alcuni dei motivi per cui l'ho fatto in questo modo; mi rende più semplice e chiaro. Tendo ad adattare Python al mio modo di scrivere codice :-).
Abbafei,

ma non funzionerà con generatori infiniti: P vedi la mia risposta :) suggerimento:tee
Janus Troelsen,

@JanusTroelsen Me ne rendo conto :-). In realtà l'ho fatto al contrario (con True / False nel set e iterabile nel metodo param) all'inizio, ma mi sono reso conto che questo non avrebbe funzionato con generatori infiniti e un utente potrebbe non rendersene conto (dal momento che questo non è (ancora) menzionato nei documenti per i parametri del metodo set iterabile), e almeno così è ovvio che non richiederà infiniti iteratori. Ne ero a conoscenza itertools.teema 1) stavo cercando un one-liner che fosse semplice / abbastanza piccolo da giustificare il copia-incolla, 2) hai già dato una risposta che utilizza quella tecnica :-)
Abbafei
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.