Sintaxis de Python para "si a o b o c pero no todos"

130

Tengo un script de Python que puede recibir cero o tres argumentos de línea de comando. (Se ejecuta con el comportamiento predeterminado o necesita los tres valores especificados).

¿Cuál es la sintaxis ideal para algo como:

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

?

Chris Wilson
fuente
44
tal vez comience con algo como `if len (sys.argv) == 0:
Edgar Aroutiounian
66
@EdgarAroutiounian len(sys.argv)siempre será al menos 1: incluye el ejecutable como argv[0].
RoadieRich
10
El cuerpo de la pregunta no coincide con el título de la pregunta. ¿Desea marcar "si a o b o c pero no todos" o "si es exactamente uno de a, byc" (como lo hace la expresión que dio)?
Doug McClean
2
¿Qué puedes decir sobre a + b + c?
gukoff
66
Espera, pregunta, puede tomar cero o tres argumentos. ¿No podrías simplemente decir if not (a and b and c)(cero argumentos) y luego if a and b and c(los tres argumentos)?
acólito

Respuestas:

236

Si te refieres a una forma mínima, ve con esto:

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

Lo que traduce el título de su pregunta.

ACTUALIZACIÓN: como dicen correctamente Volatility y Supr, puede aplicar la ley de De Morgan y obtener un equivalente:

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

Mi consejo es usar el formulario que sea más significativo para usted y para otros programadores. El primero significa "hay algo falso, pero también algo verdadero" , el segundo "hay algo verdadero, pero no todo" . Si tuviera que optimizar o hacer esto en hardware, elegiría el segundo, aquí solo elegir el más legible (también teniendo en cuenta las condiciones que probará y sus nombres). Elegí el primero.

Stefano Sanfilippo
fuente
3
Todas excelentes respuestas, pero esto gana por concisión, con un gran cortocircuito. ¡Gracias a todos!
Chris Wilson
38
Lo haría aún más conciso e iría conif not (a and b and c) and (a or b or c)
Volatility
208
O incluso if (a or b or c) and not (a and b and c)para que coincida perfectamente con el título;)
Supr
3
@ HennyH Creo que la pregunta pide "al menos una condición verdadera pero no todas", no "solo una condición verdadera".
Volatilidad
63
@Suprif any([a,b,c]) and not all([a,b,c])
eternalmatt
238

Qué tal si:

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

Otra variante:

if 1 <= sum(map(bool, conditions)) <= 2:
   ...
defuz
fuente
2
sum(conditions)puede salir mal si alguno de ellos vuelve, 2por ejemplo, que es True.
eumiro
77
Es cierto que necesitarías un feosum(map(bool, conditions))
jamylak
55
Tenga en cuenta que esto no es un cortocircuito, porque todas las condiciones se evalúan previamente.
georg
14
@PaulScheltema El primer formulario es más comprensible para cualquiera.
cmh
66
Este "cualquiera y no todo" es el mejor y más claro de los métodos booleanos, solo tenga en cuenta la importante distinción entre un argumento que está presente y un argumento que es 'verdadero'
wim
115

Esta pregunta ya tenía muchas respuestas altamente votadas y una respuesta aceptada, pero todas hasta ahora estaban distraídas por varias formas de expresar el problema booleano y perdieron un punto crucial:

Tengo un script de Python que puede recibir cero o tres argumentos de línea de comando. (Se ejecuta con el comportamiento predeterminado o necesita los tres valores especificados)

Esta lógica no debe ser responsabilidad de su código en primer lugar , sino que debe ser manejada por elargparsemódulo. No se moleste en escribir una declaración if compleja, en su lugar, prefiera configurar su analizador de argumentos de la siguiente manera:

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

Y sí, debería ser una opción, no un argumento posicional, porque después de todo es opcional .


editado: para abordar la preocupación de LarsH en los comentarios, a continuación se muestra un ejemplo de cómo podría escribirlo si estaba seguro de que deseaba la interfaz con 3 o 0argumentos posicionales . Soy de la opinión de que la interfaz anterior es mejor estilo, porque losargumentos opcionales deberían ser opciones , pero aquí hay un enfoque alternativo en aras de la integridad. Tenga en cuenta la omisión de kwargusageal crear su analizador, porque deargparselo contrario generará automáticamente un mensaje de uso engañoso.

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

Aquí hay algunos ejemplos de uso:

# 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
wim
fuente
44
Sí, lo agregué intencionalmente. Sería posible hacer que el argumento sea posicional y exigir que se consuman exactamente 3 o 0, pero no sería un buen CLI, por lo que no lo he recomendado.
wim
8
Problema separado. No cree que sea una buena CLI, y puede argumentar sobre ese punto, y el OP puede ser persuadido. Pero su respuesta se desvía de la pregunta de manera tan significativa que es necesario mencionar el cambio de especificaciones. Parece que está doblando la especificación para que se ajuste a la herramienta disponible, sin mencionar el cambio.
LarsH
2
@LarsH OK, agregué un ejemplo que encaja mejor con la interfaz original implícita en la pregunta. Ahora está doblando la herramienta para cumplir con las especificaciones disponibles ...;)
wim
2
Esta es la única respuesta que voté. +1 por responder la pregunta real .
Jonathon Reinhart
1
+1. La forma de la CLI es un tema tangencial importante, no completamente separado como dijo otra persona. Elegí tu publicación al igual que otras, la tuya es la raíz del problema y ofrece una solución elegante, mientras que otras publicaciones responden la pregunta literal. Y ambos tipos de respuestas son útiles y merecen +1.
Ben Lee
32

Yo iría por:

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

Creo que esto debería cortocircuitar de manera bastante eficiente

Explicación

Al hacer condsun iterador, el primer uso de anyhará un cortocircuito y dejará el iterador apuntando al siguiente elemento si algún elemento es verdadero; de lo contrario, consumirá toda la lista y será False. El siguiente anytoma los elementos restantes en el iterable, y se asegura de que no haya otros valores verdaderos ... Si los hay, toda la declaración no puede ser verdadera, por lo tanto no hay un elemento único (por lo tanto, cortocircuitos de nuevo). El último anyregresará Falseo agotará el iterable y será True.

nota: lo anterior verifica si solo se establece una condición


Si desea verificar si uno o más elementos, pero no todos los elementos están configurados, puede usar:

not all(conds) and any(conds)
Jon Clements
fuente
55
No lo entiendo Se lee como: si es verdadero y no verdadero. Ayúdame a entender.
rGil
1
@rGil: se lee como "si algunas manzanas son rojas y otras no", es lo mismo que decir "algunas manzanas son rojas, pero no todas".
georg
2
Incluso con una explicación no puedo entender el comportamiento ... [a, b, c] = [True, True, False]¿No debería su código "imprime" False, mientras que la salida esperada es True?
Awesoon
66
Esto es bastante inteligente, PERO: usaría este enfoque si no supiera cuántas condiciones tenía por adelantado, pero para una lista fija y conocida de condicionales, la pérdida de legibilidad simplemente no vale la pena.
esponjoso
44
Esto no hace corto circuito. La lista está completamente construida antes de pasarla a iter. anyy allva a consumir perezosamente la lista, es cierto, ¡pero la lista ya estaba completamente evaluada cuando llegas allí!
icktoofay
22

La oración en inglés:

"Si a o b o c pero no todos"

Se traduce a esta lógica:

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

La palabra "pero" generalmente implica una conjunción, en otras palabras, "y". Además, "todos ellos" se traduce en una conjunción de condiciones: esta condición, y esa condición, y otra condición. El "no" invierte toda esa conjunción.

No estoy de acuerdo con la respuesta aceptada. El autor descuidó aplicar la interpretación más directa a la especificación y descuidó aplicar la Ley de De Morgan para simplificar la expresión a menos operadores:

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

mientras afirma que la respuesta es una "forma mínima".

Kaz
fuente
En realidad, esa forma es mínima. Es la forma mínima de PoS para la expresión.
Stefano Sanfilippo
10

Esto regresa Truesi una y solo una de las tres condiciones es True. Probablemente lo que quería en su código de ejemplo.

if sum(1 for x in (a,b,c) if x) == 1:
eumiro
fuente
No es tan bonita como la respuesta de @defuz
jamylak
10

¿Qué pasa con: (condición única)

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

Tenga en cuenta que si permite dos condiciones también podría hacerlo

if (bool(a) + bool(b) + bool(c) in [1,2]):
Casimir et Hippolyte
fuente
1
Para el registro, la pregunta pide dos condiciones. Al menos uno, pero no todos = 1 de todos o 2 de todos
Marius Balčytis
En mi humilde opinión, debe deletrear el segundo como 1 <= bool(a) + bool(b) + bool(c) <= 2.
Vuelva a instalar a Monica
6

Para ser claros, ¿desea tomar su decisión en función de la cantidad de parámetros que son VERDADEROS lógicos (en caso de argumentos de cadena, no vacíos)?

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

Entonces tomaste una decisión:

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

Ahora la lógica es más clara.

Marinero danubiano
fuente
5

¿Y por qué no solo contarlos?

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

Si no le importa ser un poco críptico, puede rodar de manera simple con 0 < (a + b + c) < 3lo que regresará truesi tiene entre una y dos declaraciones verdaderas y falso si todas son falsas o ninguna es falsa.

Esto también se simplifica si usa funciones para evaluar los bools, ya que solo evalúa las variables una vez y eso significa que puede escribir las funciones en línea y no necesita almacenar temporalmente las variables. (Ejemplo:. 0 < ( a(x) + b(x) + c(x) ) < 3)

Spinno
fuente
4

La pregunta indica que necesita los tres argumentos (a y byc) o ninguno de ellos (no (a o b o c))

Esto da:

(a y byc) o no (a o b o c)

Relajarse en Chipre
fuente
4

Según tengo entendido, tiene una función que recibe 3 argumentos, pero si no lo hace, se ejecutará con el comportamiento predeterminado. Como no ha explicado lo que debería suceder cuando se proporcionan 1 o 2 argumentos, asumiré que simplemente debería hacer el comportamiento predeterminado. En cuyo caso, creo que encontrará la siguiente respuesta muy ventajosa:

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

Sin embargo, si desea que 1 o 2 argumentos se manejen de manera diferente:

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: Esto supone que " False" los valores no se pasarán a este método.

Inbar Rose
fuente
verificar el valor de verdad de un argumento es un asunto diferente de verificar si un argumento está presente o ausente
wim
@wim Entonces está convirtiendo una pregunta para adaptarla a su respuesta. El módulo argparse no tiene nada que ver con la pregunta, agrega otra importación, y si el OP no planea usar argparse en absoluto, no los ayudará en absoluto. Además, si el "script" no es independiente, sino un módulo o una función dentro de un conjunto de código más grande, es posible que ya tenga un analizador de argumentos, y esta función particular dentro de ese script más grande puede ser predeterminada o personalizada. Debido a la información limitada del OP, no puedo saber cómo debería actuar el método, pero es seguro asumir que el OP no está pasando bools.
Inbar Rose
La pregunta decía explícitamente "Tengo un script de Python que puede recibir cero o tres argumentos de línea de comando", no dice "Tengo una función que recibe 3 argumentos". Dado que el módulo argparse es la forma preferida de manejar argumentos de línea de comando en python, automáticamente tiene todo que ver con la pregunta. Por último, python es "baterías incluidas": no hay ningún inconveniente con "agregar otra importación" cuando ese módulo forma parte de las bibliotecas estándar.
wim
@wim La pregunta no está clara (el cuerpo no coincide con el título, por ejemplo). Creo que la pregunta no es lo suficientemente clara como para que esta sea una respuesta válida para alguna interpretación de la misma.
Restablezca a Monica
2

Si trabaja con un iterador de condiciones, el acceso puede ser lento. Pero no necesita acceder a cada elemento más de una vez, y no siempre necesita leerlo todo. Aquí hay una solución que funcionará con generadores infinitos:

#!/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])
Janus Troelsen
fuente
0

Cuando cada dado booles True, o cuando cada dado booles False... ¡
todos son iguales entre sí!

Por lo tanto, solo necesitamos encontrar dos elementos que evalúen diferentes bools
para saber que hay al menos uno Truey al menos uno False.

Mi solución corta:

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

Creo que es un cortocircuito, porque AFAIK a==b==ces igual a==b and b==c.

Mi solución generalizada:

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)

También escribí un código que trata con múltiples iterables, pero lo eliminé de aquí porque creo que no tiene sentido. Sin embargo, todavía está disponible aquí .

GingerPlusPlus
fuente
-2

Esto es básicamente una funcionalidad de "algunos (pero no todos)" (en contraste con las funciones incorporadas any()y all()).

Esto implica que debe haber Falses y True s entre los resultados. Por lo tanto, puede hacer lo siguiente:

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)

Una ventaja de este código es que solo necesita iterar una vez a través de los elementos resultantes (booleanos).

Una desventaja es que todas estas expresiones de verdad siempre se evalúan y no hacen cortocircuitos como los operadores or/ and.

Abbafei
fuente
1
Creo que esto es una complicación innecesaria. ¿Por qué un conjunto congelado en lugar de un conjunto antiguo simple? ¿Por qué, en .issupersetlugar de solo verificar la longitud 2, de booltodos modos no puede devolver nada más que Verdadero y Falso? ¿Por qué asignar una lambda (léase: función anónima) a un nombre en lugar de simplemente usar un def?
wim
1
La sintaxis lambda es más lógica para algunos. que son la misma longitud de todos modos ya que se necesita returnsi se utiliza def. Creo que la generalidad de esta solución es agradable. no es necesario restringirnos a los booleanos, la pregunta esencialmente es "¿cómo me aseguro de que todos estos elementos ocurran en mi lista". ¿Por qué setsi no necesitas la mutabilidad? Más inmutabilidad siempre es mejor si no necesita el rendimiento.
Janus Troelsen
@JanusTroelsen ¡Estás justo en el blanco! Estas son algunas razones por las que lo hice de esta manera; me lo hace más fácil y claro. Tiendo a adaptar Python a mi forma de codificar :-).
Abbafei
pero no funcionará en generadores infinitos: P vea mi respuesta :) pista:tee
Janus Troelsen
@JanusTroelsen Me doy cuenta de esto :-). De hecho, al principio lo tenía al revés (con True / False en el conjunto y el iterable en el parámetro del método) al principio, pero me di cuenta de que esto no funcionaría con generadores infinitos, y un usuario podría no darse cuenta (ya que este hecho no es (todavía) mencionado en los documentos para parámetros de métodos de conjuntos iterables), y al menos así es obvio que no tomará iteradores infinitos. Era consciente de, itertools.teepero 1) estaba buscando una línea que fuera simple / lo suficientemente pequeña como para justificar el pegado de copias, 2) ya dio una respuesta que usa esa técnica: -)
Abbafei