Синтаксис Python для " Если a или b или c, но не все из них"

У меня есть скрипт python, который может получать либо ноль, либо три аргумента командной строки. (Либо он работает по умолчанию, либо требуется указать все три значения.)

каков идеальный синтаксис для чего-то вроде:

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

?

15 ответов


Если вы имеете в виду минимальную форму, перейдите к этому:

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

что означает название вашего вопроса.

UPDATE: как правильно сказано волатильностью и Supr, вы можете применить закон де Моргана и получить эквивалент:

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

мой совет-использовать ту форму, которая более важна для вас и других программистов. Первое означает "есть что-то ложное, но также и что-то истинное", второй " есть что-то истинное, но не все!"--10-->. Если бы я должен был оптимизировать или сделать это в аппаратном обеспечении, я бы выбрал второй, здесь просто выберите наиболее читаемый (также принимая во внимание условия, которые вы будете тестировать, и их имена). Я выбрал первый.


Как насчет:

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

другой вариант:

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

на этот вопрос уже было много высоко оцененных ответов и принятого ответа, но все они до сих пор были отвлечены различными способами выражения булевой проблемы и пропустили важный момент:

у меня есть скрипт python, который может получать либо ноль, либо три команды аргументы строки. (Либо он работает по умолчанию, либо нуждается во всех трех значения)

эта логика не должна быть ответственность за свой код первое место, а это должно быть обработано argparse модуль. Не беспокойтесь о написании сложного оператора if, вместо этого предпочитайте настроить свой парсер аргументов примерно так:

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

и да, это должно быть опции не позиционный аргумент, потому что это ведь дополнительно.


редактировать: для решения проблемы Ларша в комментариях ниже приведен пример как вы могли бы написать его, если бы Вы были уверены, что хотите интерфейс с 3 или 0 позиционные args. Я придерживаюсь мнения, что предыдущий интерфейс лучше, потому что дополнительно аргументы должны быть опции, но вот альтернативный подход для полноты. Обратите внимание на переопределение kwarg usage при создании парсера, потому что argparse автоматически создаст ложное сообщение об использовании иначе!

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

вот несколько примеров использования:

# 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

Я пойду:

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

Я думаю, что это короткое замыкание довольно эффективно

объяснение

делая conds итератор, первое использование any будет короткое замыкание и оставить итератор, указывающий на следующий элемент, если любой элемент имеет значение true; в противном случае, он будет потреблять весь список и быть False. Следующий any принимает оставшиеся элементы в iterable и удостоверяется, что нет никаких других истинных значений... Если есть, то все утверждение не может быть истинным, поэтому нет ни одного уникального элемента (снова короткие замыкания). Последний any либо вернется False или исчерпает iterable и будет True.

Примечание: выше проверяет, установлено ли только одно условие


если вы хотите проверить, установлен ли один или несколько элементов, но не каждый элемент, вы можете использовать:

not all(conds) and any(conds)

английское предложение:

"Если a или b или c, но не все из них"

переводит на эту логику:

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

слово " но "обычно подразумевает соединение, другими словами"и". Кроме того, "все они" переводится как сочетание условий: это условие,и что условие и другое условие. "Не" инвертирует всю эту конъюнкцию.

Я не согласен, что ответ принят. Автор пренебрег самым простым толкованием спецификации и пренебрег законом де Моргана, чтобы упростить выражение для меньшего числа операторов:

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

утверждая, что ответ является "минимальной формой".


возвращает True если одно и только одно из трех условий True. Вероятно, то, что вы хотели в своем примере кода.

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

Как насчет: (уникальное состояние)

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

обратите внимание, если вы позволяете два условия тоже вы могли бы сделать это

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

чтобы быть ясным, вы хотите принять решение, основанное на том, сколько параметров логически истинно (в случае строковых аргументов - не пусто)?

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

тогда вы приняли решение:

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

теперь логика более понятна.


а почему бы просто не считать их ?

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

Если вы не против быть немного загадочным, вы можете simly roll с 0 < (a + b + c) < 3 что вернет true если у вас есть между одним и двумя истинными утверждениями и false, если все false или none false.

Это также упрощает, если вы используете функции для оценки bools, поскольку вы оцениваете переменные только один раз, и это означает, что вы можете написать функции inline и не нужно временно хранить переменные. (Пример: 0 < ( a(x) + b(x) + c(x) ) < 3.)


в вопросе говорится, что вам нужны либо все три аргумента (a и b и c), либо ни один из них (не (a или b или c))

Это дает:

(a и b и c) или нет (a или b или c)


как я понимаю, у вас есть функция, которая получает 3 аргумента, но если это не так, она будет работать по умолчанию. Поскольку вы не объяснили, что должно произойти при 1 или 2 аргументах, я предположу, что он должен просто выполнять поведение по умолчанию. В этом случае, я думаю, вы найдете ответ очень выгодное:

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

однако, если вы хотите, чтобы 1 или 2 аргумента обрабатывались по-разному:

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

Примечание: этот предполагает, что"False" ценности не передаются в этот метод.


Если вы работаете с итератором условий, он может быть медленным для доступа. Но вам не нужно обращаться к каждому элементу более одного раза, и вам не всегда нужно читать все это. Вот решение, которое будет работать с бесконечными генераторами:

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

когда каждый данный bool и True, или когда каждый данный bool и False...
они все равны друг другу!

Итак, нам просто нужно найти два элемента, которые оцениваются по-разному bools
знать, что есть хотя бы один True и хотя бы один False.

мое короткое решение:

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

Я верю в это короткое замыкание, потому что AFAIK a==b==c равна 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)

Я также написал код, касающийся нескольких итераций, но я удалил его отсюда, потому что я думаю, что это бессмысленно. Однако он по-прежнему доступен здесь.


это в основном" некоторые (но не все) " функции (в отличие от any() и all() встроенные функции).

это означает, что должно быть Falses и Trues среди результатов. Поэтому вы можете сделать следующее:

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)

одним из преимуществ этого кода является то, что вам нужно только повторить один раз через результирующие (логические) элементы.

один недостаток заключается в том, что все эти выражения истины всегда оценивают, а не делают короткое замыкание как or/and операторы.